mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-23 04:55:15 +00:00
Merge branch 'LadybirdBrowser:master' into master
This commit is contained in:
commit
cca6e845dc
1176 changed files with 42777 additions and 7354 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1,2 +1,3 @@
|
|||
custom: https://donorbox.org/ladybird
|
||||
open_collective: ladybird
|
||||
polar: LadybirdBrowser
|
||||
|
|
|
@ -46,9 +46,6 @@ endif()
|
|||
|
||||
serenity_lib(AK ak)
|
||||
|
||||
serenity_install_headers(AK)
|
||||
serenity_install_sources(AK)
|
||||
|
||||
include(stacktrace)
|
||||
configure_file(Backtrace.h.in Backtrace.h @ONLY)
|
||||
link_stacktrace_library(AK STD_DEFINITION AK_HAS_STD_STACKTRACE)
|
||||
|
|
102
AK/Function.h
102
AK/Function.h
|
@ -2,6 +2,7 @@
|
|||
* Copyright (C) 2016 Apple Inc. All rights reserved.
|
||||
* Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
|
||||
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2025, Andrew Kaster <andrew@ladybird.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@ -34,8 +35,14 @@
|
|||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
// BlockRuntime methods for Objective-C block closure support.
|
||||
extern "C" void* _Block_copy(void const*);
|
||||
extern "C" void _Block_release(void const*);
|
||||
extern "C" size_t Block_size(void const*);
|
||||
|
||||
namespace AK {
|
||||
|
||||
// These annotations are used to avoid capturing a variable with local storage in a lambda that outlives it
|
||||
|
@ -48,6 +55,17 @@ namespace AK {
|
|||
# define IGNORE_USE_IN_ESCAPING_LAMBDA
|
||||
#endif
|
||||
|
||||
namespace Detail {
|
||||
#ifdef AK_HAS_OBJC_ARC
|
||||
inline constexpr bool HaveObjcArc = true;
|
||||
#else
|
||||
inline constexpr bool HaveObjcArc = false;
|
||||
#endif
|
||||
|
||||
// validated in TestFunction.mm
|
||||
inline constexpr size_t block_layout_size = 32;
|
||||
}
|
||||
|
||||
template<typename>
|
||||
class Function;
|
||||
|
||||
|
@ -84,7 +102,7 @@ public:
|
|||
if (!m_size)
|
||||
return {};
|
||||
if (auto* wrapper = callable_wrapper())
|
||||
return ReadonlyBytes { wrapper, m_size };
|
||||
return ReadonlyBytes { wrapper->raw_callable(), m_size };
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -102,6 +120,13 @@ public:
|
|||
init_with_callable(move(f), CallableKind::FunctionPointer);
|
||||
}
|
||||
|
||||
template<typename BlockType>
|
||||
Function(BlockType b)
|
||||
requires((IsBlockClosure<BlockType> && IsCallableWithArguments<BlockType, Out, In...>))
|
||||
{
|
||||
init_with_callable(move(b), CallableKind::Block);
|
||||
}
|
||||
|
||||
Function(Function&& other)
|
||||
{
|
||||
move_from(move(other));
|
||||
|
@ -141,6 +166,15 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
template<typename BlockType>
|
||||
Function& operator=(BlockType&& block)
|
||||
requires((IsBlockClosure<BlockType> && IsCallableWithArguments<BlockType, Out, In...>))
|
||||
{
|
||||
clear();
|
||||
init_with_callable(static_cast<RemoveCVReference<BlockType>>(block), CallableKind::Block);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Function& operator=(nullptr_t)
|
||||
{
|
||||
clear();
|
||||
|
@ -160,6 +194,7 @@ private:
|
|||
enum class CallableKind {
|
||||
FunctionPointer,
|
||||
FunctionObject,
|
||||
Block,
|
||||
};
|
||||
|
||||
class CallableWrapperBase {
|
||||
|
@ -169,6 +204,7 @@ private:
|
|||
virtual Out call(In...) = 0;
|
||||
virtual void destroy() = 0;
|
||||
virtual void init_and_swap(u8*, size_t) = 0;
|
||||
virtual void const* raw_callable() const = 0;
|
||||
};
|
||||
|
||||
template<typename CallableType>
|
||||
|
@ -189,7 +225,15 @@ private:
|
|||
|
||||
void destroy() final override
|
||||
{
|
||||
delete this;
|
||||
if constexpr (IsBlockClosure<CallableType>) {
|
||||
if constexpr (Detail::HaveObjcArc)
|
||||
m_callable = nullptr;
|
||||
else
|
||||
_Block_release(m_callable);
|
||||
} else {
|
||||
// This code is a bit too clever for gcc. Pinky promise we're only deleting heap objects.
|
||||
AK_IGNORE_DIAGNOSTIC("-Wfree-nonheap-object", delete this);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter) False positive; destination is used in a placement new expression
|
||||
|
@ -199,6 +243,14 @@ private:
|
|||
new (destination) CallableWrapper { move(m_callable) };
|
||||
}
|
||||
|
||||
void const* raw_callable() const final override
|
||||
{
|
||||
if constexpr (IsBlockClosure<CallableType>)
|
||||
return static_cast<u8 const*>(bridge_cast<void>(m_callable)) + Detail::block_layout_size;
|
||||
else
|
||||
return &m_callable;
|
||||
}
|
||||
|
||||
private:
|
||||
CallableType m_callable;
|
||||
};
|
||||
|
@ -207,6 +259,7 @@ private:
|
|||
NullPointer,
|
||||
Inline,
|
||||
Outline,
|
||||
Block,
|
||||
};
|
||||
|
||||
CallableWrapperBase* callable_wrapper() const
|
||||
|
@ -215,6 +268,7 @@ private:
|
|||
case FunctionKind::NullPointer:
|
||||
return nullptr;
|
||||
case FunctionKind::Inline:
|
||||
case FunctionKind::Block:
|
||||
return bit_cast<CallableWrapperBase*>(&m_storage);
|
||||
case FunctionKind::Outline:
|
||||
return *bit_cast<CallableWrapperBase**>(&m_storage);
|
||||
|
@ -234,12 +288,22 @@ private:
|
|||
}
|
||||
m_deferred_clear = false;
|
||||
auto* wrapper = callable_wrapper();
|
||||
if (m_kind == FunctionKind::Inline) {
|
||||
switch (m_kind) {
|
||||
case FunctionKind::Inline:
|
||||
VERIFY(wrapper);
|
||||
wrapper->~CallableWrapperBase();
|
||||
} else if (m_kind == FunctionKind::Outline) {
|
||||
break;
|
||||
case FunctionKind::Outline:
|
||||
VERIFY(wrapper);
|
||||
wrapper->destroy();
|
||||
break;
|
||||
case FunctionKind::Block:
|
||||
VERIFY(wrapper);
|
||||
wrapper->destroy();
|
||||
wrapper->~CallableWrapperBase();
|
||||
break;
|
||||
case FunctionKind::NullPointer:
|
||||
break;
|
||||
}
|
||||
m_kind = FunctionKind::NullPointer;
|
||||
}
|
||||
|
@ -256,18 +320,33 @@ private:
|
|||
}
|
||||
VERIFY(m_call_nesting_level == 0);
|
||||
using WrapperType = CallableWrapper<Callable>;
|
||||
if (callable_kind == CallableKind::FunctionObject)
|
||||
m_size = sizeof(Callable);
|
||||
else
|
||||
m_size = 0;
|
||||
if constexpr (IsBlockClosure<Callable>) {
|
||||
auto block_size = Block_size(bridge_cast<void>(callable));
|
||||
VERIFY(block_size >= Detail::block_layout_size);
|
||||
m_size = block_size - Detail::block_layout_size;
|
||||
}
|
||||
|
||||
if constexpr (alignof(Callable) > inline_alignment || sizeof(WrapperType) > inline_capacity) {
|
||||
*bit_cast<CallableWrapperBase**>(&m_storage) = new WrapperType(forward<Callable>(callable));
|
||||
m_kind = FunctionKind::Outline;
|
||||
} else {
|
||||
static_assert(sizeof(WrapperType) <= inline_capacity);
|
||||
new (m_storage) WrapperType(forward<Callable>(callable));
|
||||
m_kind = FunctionKind::Inline;
|
||||
if constexpr (IsBlockClosure<Callable>) {
|
||||
if constexpr (Detail::HaveObjcArc) {
|
||||
new (m_storage) WrapperType(forward<Callable>(callable));
|
||||
} else {
|
||||
new (m_storage) WrapperType(reinterpret_cast<Callable>(_Block_copy(callable)));
|
||||
}
|
||||
m_kind = FunctionKind::Block;
|
||||
} else {
|
||||
new (m_storage) WrapperType(forward<Callable>(callable));
|
||||
m_kind = FunctionKind::Inline;
|
||||
}
|
||||
}
|
||||
if (callable_kind == CallableKind::FunctionObject)
|
||||
m_size = sizeof(WrapperType);
|
||||
else
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void move_from(Function&& other)
|
||||
|
@ -279,8 +358,9 @@ private:
|
|||
case FunctionKind::NullPointer:
|
||||
break;
|
||||
case FunctionKind::Inline:
|
||||
case FunctionKind::Block:
|
||||
other_wrapper->init_and_swap(m_storage, inline_capacity);
|
||||
m_kind = FunctionKind::Inline;
|
||||
m_kind = other.m_kind;
|
||||
break;
|
||||
case FunctionKind::Outline:
|
||||
*bit_cast<CallableWrapperBase**>(&m_storage) = other_wrapper;
|
||||
|
|
|
@ -41,6 +41,12 @@ template<typename T, typename... Ts>
|
|||
return (... || (forward<T>(to_compare) >= forward<Ts>(valid_values)));
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
[[nodiscard]] constexpr bool first_is_equal_to_all_of(T&& to_compare, Ts&&... valid_values)
|
||||
{
|
||||
return (... && (forward<T>(to_compare) == forward<Ts>(valid_values)));
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
[[nodiscard]] constexpr bool first_is_smaller_than_all_of(T&& to_compare, Ts&&... valid_values)
|
||||
{
|
||||
|
@ -67,6 +73,7 @@ template<typename T, typename... Ts>
|
|||
}
|
||||
|
||||
#if USING_AK_GLOBALLY
|
||||
using AK::first_is_equal_to_all_of;
|
||||
using AK::first_is_larger_or_equal_than_all_of;
|
||||
using AK::first_is_larger_or_equal_than_one_of;
|
||||
using AK::first_is_larger_than_all_of;
|
||||
|
|
|
@ -18,6 +18,12 @@ constexpr bool is_space(int ch)
|
|||
return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' ';
|
||||
}
|
||||
|
||||
ErrorOr<JsonValue> JsonParser::parse(StringView input)
|
||||
{
|
||||
JsonParser parser(input);
|
||||
return parser.parse_json();
|
||||
}
|
||||
|
||||
// ECMA-404 9 String
|
||||
// Boils down to
|
||||
// STRING = "\"" *("[^\"\\]" | "\\" ("[\"\\bfnrt]" | "u[0-9A-Za-z]{4}")) "\""
|
||||
|
@ -335,7 +341,7 @@ ErrorOr<JsonValue> JsonParser::parse_helper()
|
|||
return Error::from_string_literal("JsonParser: Unexpected character");
|
||||
}
|
||||
|
||||
ErrorOr<JsonValue> JsonParser::parse()
|
||||
ErrorOr<JsonValue> JsonParser::parse_json()
|
||||
{
|
||||
auto result = TRY(parse_helper());
|
||||
ignore_while(is_space);
|
||||
|
|
|
@ -13,14 +13,15 @@ namespace AK {
|
|||
|
||||
class JsonParser : private GenericLexer {
|
||||
public:
|
||||
static ErrorOr<JsonValue> parse(StringView);
|
||||
|
||||
private:
|
||||
explicit JsonParser(StringView input)
|
||||
: GenericLexer(input)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<JsonValue> parse();
|
||||
|
||||
private:
|
||||
ErrorOr<JsonValue> parse_json();
|
||||
ErrorOr<JsonValue> parse_helper();
|
||||
|
||||
ErrorOr<ByteString> consume_and_unescape_string();
|
||||
|
|
|
@ -190,7 +190,7 @@ JsonValue::JsonValue(JsonArray&& value)
|
|||
|
||||
ErrorOr<JsonValue> JsonValue::from_string(StringView input)
|
||||
{
|
||||
return JsonParser(input).parse();
|
||||
return JsonParser::parse(input);
|
||||
}
|
||||
|
||||
String JsonValue::serialized() const
|
||||
|
|
|
@ -265,6 +265,14 @@
|
|||
# define LSAN_UNREGISTER_ROOT_REGION(base, size)
|
||||
#endif
|
||||
|
||||
#if __has_feature(blocks)
|
||||
# define AK_HAS_BLOCKS
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
# define AK_HAS_OBJC_ARC
|
||||
#endif
|
||||
|
||||
#ifndef AK_OS_SERENITY
|
||||
# ifdef AK_OS_WINDOWS
|
||||
// FIXME: No idea where to get this, but it's 4096 anyway :^)
|
||||
|
|
|
@ -142,6 +142,27 @@ inline constexpr bool IsFunction<Ret(Args...) const volatile&&> = true;
|
|||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsFunction<Ret(Args..., ...) const volatile&&> = true;
|
||||
|
||||
template<class>
|
||||
inline constexpr bool IsBlockClosure = false;
|
||||
#ifdef AK_HAS_BLOCKS
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^)(Args...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^)(Args..., ...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^const)(Args...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^const)(Args..., ...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^volatile)(Args...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^volatile)(Args..., ...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^const volatile)(Args...)> = true;
|
||||
template<class Ret, class... Args>
|
||||
inline constexpr bool IsBlockClosure<Ret (^const volatile)(Args..., ...)> = true;
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
inline constexpr bool IsRvalueReference = false;
|
||||
template<class T>
|
||||
|
@ -641,6 +662,7 @@ using AK::Detail::InvokeResult;
|
|||
using AK::Detail::IsArithmetic;
|
||||
using AK::Detail::IsAssignable;
|
||||
using AK::Detail::IsBaseOf;
|
||||
using AK::Detail::IsBlockClosure;
|
||||
using AK::Detail::IsClass;
|
||||
using AK::Detail::IsConst;
|
||||
using AK::Detail::IsConstructible;
|
||||
|
|
|
@ -73,10 +73,21 @@ ALWAYS_INLINE CopyConst<InputType, OutputType>* as(InputType* input)
|
|||
return result;
|
||||
}
|
||||
|
||||
template<typename OutputType, typename InputType>
|
||||
ALWAYS_INLINE CopyConst<InputType, OutputType>* bridge_cast(InputType input)
|
||||
{
|
||||
#ifdef AK_HAS_OBJC_ARC
|
||||
return (__bridge CopyConst<InputType, OutputType>*)(input);
|
||||
#else
|
||||
return static_cast<CopyConst<InputType, OutputType>*>(input);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if USING_AK_GLOBALLY
|
||||
using AK::as;
|
||||
using AK::as_if;
|
||||
using AK::bridge_cast;
|
||||
using AK::is;
|
||||
#endif
|
||||
|
|
175
Base/res/ladybird/about-pages/processes.html
Normal file
175
Base/res/ladybird/about-pages/processes.html
Normal file
|
@ -0,0 +1,175 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Task Manager</title>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--table-border: gray;
|
||||
--table-row-odd: rgb(57, 57, 57);
|
||||
--table-row-hover: rgb(80, 79, 79);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--table-border: gray;
|
||||
--table-row-odd: rgb(229, 229, 229);
|
||||
--table-row-hover: rgb(199, 198, 198);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
|
||||
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--table-border);
|
||||
}
|
||||
|
||||
th:hover {
|
||||
background-color: var(--table-row-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th.sorted-ascending:after {
|
||||
content: " ▲";
|
||||
}
|
||||
|
||||
th.sorted-descending:after {
|
||||
content: " ▼";
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 4px;
|
||||
border: 1px solid var(--table-border);
|
||||
}
|
||||
|
||||
tbody tr:nth-of-type(2n + 1) {
|
||||
background-color: var(--table-row-odd);
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: var(--table-row-hover);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="name">Name</th>
|
||||
<th id="pid">PID</th>
|
||||
<th id="cpu">CPU</th>
|
||||
<th id="memory">Memory</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="process-table"></tbody>
|
||||
</table>
|
||||
|
||||
<script type="text/javascript">
|
||||
const cpuFormatter = new Intl.NumberFormat([], {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const memoryFormatter = new Intl.NumberFormat([], {
|
||||
style: "unit",
|
||||
unit: "byte",
|
||||
notation: "compact",
|
||||
unitDisplay: "narrow",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const Direction = Object.freeze({
|
||||
ascending: 1,
|
||||
descending: 2,
|
||||
});
|
||||
|
||||
processes.processes = [];
|
||||
processes.sortDirection = Direction.ascending;
|
||||
processes.sortKey = "pid";
|
||||
|
||||
const renderSortedProcesses = () => {
|
||||
document.querySelectorAll("th").forEach(header => {
|
||||
header.classList.remove("sorted-ascending");
|
||||
header.classList.remove("sorted-descending");
|
||||
});
|
||||
|
||||
if (processes.sortDirection === Direction.ascending) {
|
||||
document.getElementById(processes.sortKey).classList.add("sorted-ascending");
|
||||
} else {
|
||||
document.getElementById(processes.sortKey).classList.add("sorted-descending");
|
||||
}
|
||||
|
||||
const multiplier = processes.sortDirection === Direction.ascending ? 1 : -1;
|
||||
|
||||
processes.processes.sort((lhs, rhs) => {
|
||||
const lhsValue = lhs[processes.sortKey];
|
||||
const rhsValue = rhs[processes.sortKey];
|
||||
|
||||
if (typeof lhsValue === "string") {
|
||||
return multiplier * lhsValue.localeCompare(rhsValue);
|
||||
}
|
||||
|
||||
return multiplier * (lhsValue - rhsValue);
|
||||
});
|
||||
|
||||
let oldTable = document.getElementById("process-table");
|
||||
|
||||
let newTable = document.createElement("tbody");
|
||||
newTable.setAttribute("id", "process-table");
|
||||
|
||||
const insertColumn = (row, value) => {
|
||||
let column = row.insertCell();
|
||||
column.innerText = value;
|
||||
};
|
||||
|
||||
processes.processes.forEach(process => {
|
||||
let row = newTable.insertRow();
|
||||
insertColumn(row, process.name);
|
||||
insertColumn(row, process.pid);
|
||||
insertColumn(row, cpuFormatter.format(process.cpu));
|
||||
insertColumn(row, memoryFormatter.format(process.memory));
|
||||
});
|
||||
|
||||
oldTable.parentNode.replaceChild(newTable, oldTable);
|
||||
};
|
||||
|
||||
processes.loadProcessStatistics = statistics => {
|
||||
processes.processes = JSON.parse(statistics);
|
||||
renderSortedProcesses();
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll("th").forEach(header => {
|
||||
header.addEventListener("click", () => {
|
||||
processes.sortDirection = header.classList.contains("sorted-descending")
|
||||
? Direction.ascending
|
||||
: Direction.descending;
|
||||
processes.sortKey = header.getAttribute("id");
|
||||
|
||||
renderSortedProcesses();
|
||||
});
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
processes.updateProcessStatistics();
|
||||
}, 1000);
|
||||
|
||||
processes.updateProcessStatistics();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -129,14 +129,14 @@ cmake -GNinja -BBuild/release
|
|||
Finally, run `ninja` (or the generator you're using) to start the build:
|
||||
|
||||
```
|
||||
ninja -CBuild/release
|
||||
ninja -C Build/release
|
||||
```
|
||||
|
||||
For more information, see [Custom CMake build directory](#custom-cmake-build-directory) and [Running manually](#running-manually).
|
||||
|
||||
### macOS:
|
||||
|
||||
Xcode 14 versions before 14.3 might crash while building ladybird. Xcode 14.3 or clang from homebrew may be required to successfully build ladybird.
|
||||
Xcode 15 or clang from homebrew is required to successfully build ladybird.
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
|
@ -214,7 +214,7 @@ The simplest way to build and run ladybird is via the ladybird.sh script:
|
|||
|
||||
```bash
|
||||
# From /path/to/ladybird
|
||||
./Meta/ladybird.sh run ladybird
|
||||
./Meta/ladybird.sh run
|
||||
```
|
||||
|
||||
On macOS, to build using clang from homebrew:
|
||||
|
@ -231,11 +231,14 @@ The above commands will build a Release version of Ladybird. To instead build a
|
|||
`Meta/ladybird.sh` script with the value of the `BUILD_PRESET` environment variable set to `Debug`, like this:
|
||||
|
||||
```bash
|
||||
BUILD_PRESET=Debug ./Meta/ladybird.sh run ladybird
|
||||
BUILD_PRESET=Debug ./Meta/ladybird.sh run
|
||||
```
|
||||
|
||||
Note that debug symbols are available in both Release and Debug builds.
|
||||
|
||||
If you want to run other applications, such as the headless-browser, the JS REPL, or the WebAssembly REPL, specify an
|
||||
executable with `./Meta/ladybird.sh run <executable_name>`.
|
||||
|
||||
### The User Interfaces
|
||||
|
||||
Ladybird will be built with one of the following browser frontends, depending on the platform:
|
||||
|
@ -305,9 +308,9 @@ a suitable C++ compiler (g++ >= 13, clang >= 14, Apple Clang >= 14.3) via the CM
|
|||
CMAKE_C_COMPILER cmake options.
|
||||
|
||||
```
|
||||
cmake -GNinja -B MyBuildDir
|
||||
cmake --preset default -B MyBuildDir
|
||||
# optionally, add -DCMAKE_CXX_COMPILER=<suitable compiler> -DCMAKE_C_COMPILER=<matching c compiler>
|
||||
cmake --build MyBuildDir
|
||||
cmake --build --preset default MyBuildDir
|
||||
ninja -C MyBuildDir run-ladybird
|
||||
```
|
||||
|
||||
|
@ -367,15 +370,8 @@ Simply run the `ladybird.sh` script as normal, and then make sure to codesign th
|
|||
|
||||
Now you can open the Instruments app and point it to the Ladybird app bundle.
|
||||
|
||||
If you want to use Xcode itself for debugging, you will need to generate an Xcode project.
|
||||
The `ladybird.sh` build script does not know how to generate Xcode projects, so creating the project must be done manually.
|
||||
|
||||
```
|
||||
cmake -GXcode -B Build/release
|
||||
```
|
||||
|
||||
After generating an Xcode project into the specified build directory, you can open `ladybird.xcodeproj` in Xcode. The project has a ton of targets, many of which are generated code.
|
||||
The only target that needs a scheme is the ladybird app bundle.
|
||||
Building the project with Xcode is not supported. The Xcode project generated by CMake does not properly execute custom
|
||||
targets, and does not handle all target names in the project.
|
||||
|
||||
### Building on OpenIndiana
|
||||
|
||||
|
|
|
@ -59,5 +59,5 @@ bool Paintable::is_visible() const
|
|||
## JavaScript
|
||||
|
||||
Some properties have special rules for getting the computed value from JS. For these, you will need to add to
|
||||
`ResolvedCSSStyleDeclaration::style_value_for_property()`. Shorthands that are constructed in an unusual way (as in, not
|
||||
using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`.
|
||||
`CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not
|
||||
using `ShorthandStyleValue`) also need handling inside `CSSStyleProperties::get_property_internal()`.
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
set(SOURCES
|
||||
Deflate.cpp
|
||||
GenericZlib.cpp
|
||||
Gzip.cpp
|
||||
PackBitsDecoder.cpp
|
||||
Zlib.cpp
|
||||
Gzip.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibCompress compress)
|
||||
target_link_libraries(LibCompress PRIVATE LibCore LibCrypto)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(LibCompress PRIVATE ZLIB::ZLIB)
|
||||
|
|
|
@ -1,25 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <LibCompress/Deflate.h>
|
||||
#include <LibCompress/Huffman.h>
|
||||
#include <LibCompress/DeflateTables.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
static constexpr u8 deflate_special_code_length_copy = 16;
|
||||
static constexpr u8 deflate_special_code_length_zeros = 17;
|
||||
static constexpr u8 deflate_special_code_length_long_zeros = 18;
|
||||
|
||||
static constexpr int EndOfBlock = 256;
|
||||
|
||||
CanonicalCode const& CanonicalCode::fixed_literal_codes()
|
||||
{
|
||||
static CanonicalCode code;
|
||||
|
@ -165,807 +159,28 @@ ErrorOr<u32> CanonicalCode::read_symbol(LittleEndianInputBitStream& stream) cons
|
|||
return Error::from_string_literal("Symbol exceeds maximum symbol number");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<bool> DeflateDecompressor::CompressedBlock::try_read_more()
|
||||
{
|
||||
if (m_eof == true)
|
||||
return false;
|
||||
|
||||
auto const symbol = TRY(m_literal_codes.read_symbol(*m_decompressor.m_input_stream));
|
||||
|
||||
if (symbol >= 286)
|
||||
return Error::from_string_literal("Invalid deflate literal/length symbol");
|
||||
|
||||
if (symbol < EndOfBlock) {
|
||||
u8 byte_symbol = symbol;
|
||||
m_decompressor.m_output_buffer.write({ &byte_symbol, sizeof(byte_symbol) });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (symbol == EndOfBlock) {
|
||||
m_eof = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_distance_codes.has_value())
|
||||
return Error::from_string_literal("Distance codes have not been initialized");
|
||||
|
||||
auto const length = TRY(m_decompressor.decode_length(symbol));
|
||||
auto const distance_symbol = TRY(m_distance_codes.value().read_symbol(*m_decompressor.m_input_stream));
|
||||
if (distance_symbol >= 30)
|
||||
return Error::from_string_literal("Invalid deflate distance symbol");
|
||||
|
||||
auto const distance = TRY(m_decompressor.decode_distance(distance_symbol));
|
||||
|
||||
auto copied_length = TRY(m_decompressor.m_output_buffer.copy_from_seekback(distance, length));
|
||||
|
||||
// TODO: What should we do if the output buffer is full?
|
||||
VERIFY(copied_length == length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DeflateDecompressor::UncompressedBlock::UncompressedBlock(DeflateDecompressor& decompressor, size_t length)
|
||||
: m_decompressor(decompressor)
|
||||
, m_bytes_remaining(length)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<bool> DeflateDecompressor::UncompressedBlock::try_read_more()
|
||||
{
|
||||
if (m_bytes_remaining == 0)
|
||||
return false;
|
||||
|
||||
if (m_decompressor.m_input_stream->is_eof())
|
||||
return Error::from_string_literal("Input data ends in the middle of an uncompressed DEFLATE block");
|
||||
|
||||
Array<u8, 4096> temporary_buffer;
|
||||
auto readable_bytes = temporary_buffer.span().trim(min(m_bytes_remaining, m_decompressor.m_output_buffer.empty_space()));
|
||||
auto read_bytes = TRY(m_decompressor.m_input_stream->read_some(readable_bytes));
|
||||
auto written_bytes = m_decompressor.m_output_buffer.write(read_bytes);
|
||||
VERIFY(read_bytes.size() == written_bytes);
|
||||
|
||||
m_bytes_remaining -= written_bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<DeflateDecompressor>> DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream> stream)
|
||||
{
|
||||
auto output_buffer = TRY(CircularBuffer::create_empty(32 * KiB));
|
||||
return TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(stream), move(output_buffer))));
|
||||
}
|
||||
|
||||
DeflateDecompressor::DeflateDecompressor(MaybeOwned<LittleEndianInputBitStream> stream, CircularBuffer output_buffer)
|
||||
: m_input_stream(move(stream))
|
||||
, m_output_buffer(move(output_buffer))
|
||||
{
|
||||
}
|
||||
|
||||
DeflateDecompressor::~DeflateDecompressor()
|
||||
{
|
||||
if (m_state == State::ReadingCompressedBlock)
|
||||
m_compressed_block.~CompressedBlock();
|
||||
if (m_state == State::ReadingUncompressedBlock)
|
||||
m_uncompressed_block.~UncompressedBlock();
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> DeflateDecompressor::read_some(Bytes bytes)
|
||||
{
|
||||
size_t total_read = 0;
|
||||
while (total_read < bytes.size()) {
|
||||
auto slice = bytes.slice(total_read);
|
||||
|
||||
if (m_state == State::Idle) {
|
||||
if (m_read_final_block)
|
||||
break;
|
||||
|
||||
m_read_final_block = TRY(m_input_stream->read_bit());
|
||||
auto const block_type = TRY(m_input_stream->read_bits(2));
|
||||
|
||||
if (block_type == 0b00) {
|
||||
m_input_stream->align_to_byte_boundary();
|
||||
|
||||
u16 length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
|
||||
u16 negated_length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
|
||||
|
||||
if ((length ^ 0xffff) != negated_length)
|
||||
return Error::from_string_literal("Calculated negated length does not equal stored negated length");
|
||||
|
||||
m_state = State::ReadingUncompressedBlock;
|
||||
new (&m_uncompressed_block) UncompressedBlock(*this, length);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block_type == 0b01) {
|
||||
m_state = State::ReadingCompressedBlock;
|
||||
new (&m_compressed_block) CompressedBlock(*this, CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block_type == 0b10) {
|
||||
CanonicalCode literal_codes;
|
||||
Optional<CanonicalCode> distance_codes;
|
||||
TRY(decode_codes(literal_codes, distance_codes));
|
||||
|
||||
m_state = State::ReadingCompressedBlock;
|
||||
new (&m_compressed_block) CompressedBlock(*this, literal_codes, distance_codes);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return Error::from_string_literal("Unhandled block type for Idle state");
|
||||
}
|
||||
|
||||
if (m_state == State::ReadingCompressedBlock) {
|
||||
auto nread = m_output_buffer.read(slice).size();
|
||||
|
||||
while (nread < slice.size() && TRY(m_compressed_block.try_read_more())) {
|
||||
nread += m_output_buffer.read(slice.slice(nread)).size();
|
||||
}
|
||||
|
||||
total_read += nread;
|
||||
if (nread == slice.size())
|
||||
break;
|
||||
|
||||
m_compressed_block.~CompressedBlock();
|
||||
m_state = State::Idle;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_state == State::ReadingUncompressedBlock) {
|
||||
auto nread = m_output_buffer.read(slice).size();
|
||||
|
||||
while (nread < slice.size() && TRY(m_uncompressed_block.try_read_more())) {
|
||||
nread += m_output_buffer.read(slice.slice(nread)).size();
|
||||
}
|
||||
|
||||
total_read += nread;
|
||||
if (nread == slice.size())
|
||||
break;
|
||||
|
||||
m_uncompressed_block.~UncompressedBlock();
|
||||
m_state = State::Idle;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
return bytes.slice(0, total_read);
|
||||
}
|
||||
|
||||
bool DeflateDecompressor::is_eof() const { return m_state == State::Idle && m_read_final_block; }
|
||||
|
||||
ErrorOr<size_t> DeflateDecompressor::write_some(ReadonlyBytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
bool DeflateDecompressor::is_open() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeflateDecompressor::close()
|
||||
ErrorOr<NonnullOwnPtr<DeflateDecompressor>> DeflateDecompressor::create(MaybeOwned<Stream> stream)
|
||||
{
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(-MAX_WBITS));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> DeflateDecompressor::decompress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
FixedMemoryStream memory_stream { bytes };
|
||||
LittleEndianInputBitStream bit_stream { MaybeOwned<Stream>(memory_stream) };
|
||||
auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream>(bit_stream)));
|
||||
return deflate_stream->read_until_eof(4096);
|
||||
return ::Compress::decompress_all<DeflateDecompressor>(bytes);
|
||||
}
|
||||
|
||||
ErrorOr<u32> DeflateDecompressor::decode_length(u32 symbol)
|
||||
ErrorOr<NonnullOwnPtr<DeflateCompressor>> DeflateCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
if (symbol <= 264)
|
||||
return symbol - 254;
|
||||
|
||||
if (symbol <= 284) {
|
||||
auto extra_bits = (symbol - 261) / 4;
|
||||
return (((symbol - 265) % 4 + 4) << extra_bits) + 3 + TRY(m_input_stream->read_bits(extra_bits));
|
||||
}
|
||||
|
||||
if (symbol == 285)
|
||||
return DeflateDecompressor::max_back_reference_length;
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibCompressor::new_z_stream(-MAX_WBITS, compression_level));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ErrorOr<u32> DeflateDecompressor::decode_distance(u32 symbol)
|
||||
ErrorOr<ByteBuffer> DeflateCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
if (symbol <= 3)
|
||||
return symbol + 1;
|
||||
|
||||
if (symbol <= 29) {
|
||||
auto extra_bits = (symbol / 2) - 1;
|
||||
return ((symbol % 2 + 2) << extra_bits) + 1 + TRY(m_input_stream->read_bits(extra_bits));
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
ErrorOr<void> DeflateDecompressor::decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code)
|
||||
{
|
||||
auto literal_code_count = TRY(m_input_stream->read_bits(5)) + 257;
|
||||
auto distance_code_count = TRY(m_input_stream->read_bits(5)) + 1;
|
||||
auto code_length_count = TRY(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) {
|
||||
code_lengths_code_lengths[code_lengths_code_lengths_order[i]] = TRY(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 const code_length_code = TRY(CanonicalCode::from_bytes({ code_lengths_code_lengths, sizeof(code_lengths_code_lengths) }));
|
||||
|
||||
// Next we extract the code lengths of the code that was used to encode the block.
|
||||
Vector<u8, 286> code_lengths;
|
||||
while (code_lengths.size() < literal_code_count + distance_code_count) {
|
||||
auto symbol = TRY(code_length_code.read_symbol(*m_input_stream));
|
||||
|
||||
if (symbol < deflate_special_code_length_copy) {
|
||||
code_lengths.append(static_cast<u8>(symbol));
|
||||
} else if (symbol == deflate_special_code_length_copy) {
|
||||
if (code_lengths.is_empty())
|
||||
return Error::from_string_literal("Found no codes to copy before a copy block");
|
||||
auto nrepeat = 3 + TRY(m_input_stream->read_bits(2));
|
||||
for (size_t j = 0; j < nrepeat; ++j)
|
||||
code_lengths.append(code_lengths.last());
|
||||
} else if (symbol == deflate_special_code_length_zeros) {
|
||||
auto nrepeat = 3 + TRY(m_input_stream->read_bits(3));
|
||||
for (size_t j = 0; j < nrepeat; ++j)
|
||||
code_lengths.append(0);
|
||||
} else {
|
||||
VERIFY(symbol == deflate_special_code_length_long_zeros);
|
||||
auto nrepeat = 11 + TRY(m_input_stream->read_bits(7));
|
||||
for (size_t j = 0; j < nrepeat; ++j)
|
||||
code_lengths.append(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (code_lengths.size() != literal_code_count + distance_code_count)
|
||||
return Error::from_string_literal("Number of code lengths does not match the sum of codes");
|
||||
|
||||
// Now we extract the code that was used to encode literals and lengths in the block.
|
||||
literal_code = TRY(CanonicalCode::from_bytes(code_lengths.span().trim(literal_code_count)));
|
||||
|
||||
// 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)
|
||||
return Error::from_string_literal("Length for a single distance code is longer than 1");
|
||||
}
|
||||
|
||||
distance_code = TRY(CanonicalCode::from_bytes(code_lengths.span().slice(literal_code_count)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<DeflateCompressor>> DeflateCompressor::construct(MaybeOwned<Stream> stream, CompressionLevel compression_level)
|
||||
{
|
||||
auto bit_stream = TRY(try_make<LittleEndianOutputBitStream>(move(stream)));
|
||||
auto deflate_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(bit_stream), compression_level)));
|
||||
return deflate_compressor;
|
||||
}
|
||||
|
||||
DeflateCompressor::DeflateCompressor(NonnullOwnPtr<LittleEndianOutputBitStream> stream, CompressionLevel compression_level)
|
||||
: m_compression_level(compression_level)
|
||||
, m_compression_constants(compression_constants[static_cast<int>(m_compression_level)])
|
||||
, m_output_stream(move(stream))
|
||||
{
|
||||
m_symbol_frequencies.fill(0);
|
||||
m_distance_frequencies.fill(0);
|
||||
}
|
||||
|
||||
DeflateCompressor::~DeflateCompressor() = default;
|
||||
|
||||
ErrorOr<Bytes> DeflateCompressor::read_some(Bytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> DeflateCompressor::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
|
||||
size_t total_written = 0;
|
||||
while (!bytes.is_empty()) {
|
||||
auto n_written = bytes.copy_trimmed_to(pending_block().slice(m_pending_block_size));
|
||||
m_pending_block_size += n_written;
|
||||
|
||||
if (m_pending_block_size == block_size)
|
||||
TRY(flush());
|
||||
|
||||
bytes = bytes.slice(n_written);
|
||||
total_written += n_written;
|
||||
}
|
||||
return total_written;
|
||||
}
|
||||
|
||||
bool DeflateCompressor::is_eof() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeflateCompressor::is_open() const
|
||||
{
|
||||
return m_output_stream->is_open();
|
||||
}
|
||||
|
||||
void DeflateCompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
// Knuth's multiplicative hash on 4 bytes
|
||||
u16 DeflateCompressor::hash_sequence(u8 const* bytes)
|
||||
{
|
||||
constexpr u32 const knuth_constant = 2654435761; // shares no common factors with 2^32
|
||||
return ((bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24) * knuth_constant) >> (32 - hash_bits);
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::compare_match_candidate(size_t start, size_t candidate, size_t previous_match_length, size_t maximum_match_length)
|
||||
{
|
||||
VERIFY(previous_match_length < maximum_match_length);
|
||||
|
||||
// We firstly check that the match is at least (prev_match_length + 1) long, we check backwards as there's a higher chance the end mismatches
|
||||
for (ssize_t i = previous_match_length; i >= 0; i--) {
|
||||
if (m_rolling_window[start + i] != m_rolling_window[candidate + i])
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the actual length
|
||||
auto match_length = previous_match_length + 1;
|
||||
while (match_length < maximum_match_length && m_rolling_window[start + match_length] == m_rolling_window[candidate + match_length]) {
|
||||
match_length++;
|
||||
}
|
||||
|
||||
VERIFY(match_length > previous_match_length);
|
||||
VERIFY(match_length <= maximum_match_length);
|
||||
return match_length;
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t maximum_match_length, size_t& match_position)
|
||||
{
|
||||
auto max_chain_length = m_compression_constants.max_chain;
|
||||
if (previous_match_length == 0)
|
||||
previous_match_length = min_match_length - 1; // we only care about matches that are at least min_match_length long
|
||||
if (previous_match_length >= maximum_match_length)
|
||||
return 0; // we can't improve a maximum length match
|
||||
if (previous_match_length >= m_compression_constants.max_lazy_length)
|
||||
return 0; // the previous match is already pretty, we shouldn't waste another full search
|
||||
if (previous_match_length >= m_compression_constants.good_match_length)
|
||||
max_chain_length /= 4; // we already have a pretty good much, so do a shorter search
|
||||
|
||||
auto candidate = m_hash_head[hash];
|
||||
auto match_found = false;
|
||||
while (max_chain_length--) {
|
||||
if (candidate == empty_slot)
|
||||
break; // no remaining candidates
|
||||
|
||||
VERIFY(candidate < start);
|
||||
if (start - candidate > window_size)
|
||||
break; // outside the window
|
||||
|
||||
auto match_length = compare_match_candidate(start, candidate, previous_match_length, maximum_match_length);
|
||||
|
||||
if (match_length != 0) {
|
||||
match_found = true;
|
||||
match_position = candidate;
|
||||
previous_match_length = match_length;
|
||||
|
||||
if (match_length == maximum_match_length)
|
||||
return match_length; // bail if we got the maximum possible length
|
||||
}
|
||||
|
||||
candidate = m_hash_prev[candidate % window_size];
|
||||
}
|
||||
if (!match_found)
|
||||
return 0; // we didn't find any matches
|
||||
return previous_match_length; // we found matches, but they were at most previous_match_length long
|
||||
}
|
||||
|
||||
ALWAYS_INLINE u8 DeflateCompressor::distance_to_base(u16 distance)
|
||||
{
|
||||
return (distance <= 256) ? distance_to_base_lo[distance - 1] : distance_to_base_hi[(distance - 1) >> 7];
|
||||
}
|
||||
|
||||
void DeflateCompressor::lz77_compress_block()
|
||||
{
|
||||
for (auto& slot : m_hash_head) { // initialize chained hash table
|
||||
slot = empty_slot;
|
||||
}
|
||||
|
||||
auto insert_hash = [&](auto pos, auto hash) {
|
||||
auto window_pos = pos % window_size;
|
||||
m_hash_prev[window_pos] = m_hash_head[hash];
|
||||
m_hash_head[hash] = window_pos;
|
||||
};
|
||||
|
||||
auto emit_literal = [&](auto literal) {
|
||||
VERIFY(m_pending_symbol_size <= block_size + 1);
|
||||
auto index = m_pending_symbol_size++;
|
||||
m_symbol_buffer[index].distance = 0;
|
||||
m_symbol_buffer[index].literal = literal;
|
||||
m_symbol_frequencies[literal]++;
|
||||
};
|
||||
|
||||
auto emit_back_reference = [&](auto distance, auto length) {
|
||||
VERIFY(m_pending_symbol_size <= block_size + 1);
|
||||
auto index = m_pending_symbol_size++;
|
||||
m_symbol_buffer[index].distance = distance;
|
||||
m_symbol_buffer[index].length = length;
|
||||
m_symbol_frequencies[length_to_symbol[length]]++;
|
||||
m_distance_frequencies[distance_to_base(distance)]++;
|
||||
};
|
||||
|
||||
size_t previous_match_length = 0;
|
||||
size_t previous_match_position = 0;
|
||||
|
||||
VERIFY(m_compression_constants.great_match_length <= max_match_length);
|
||||
|
||||
// our block starts at block_size and is m_pending_block_size in length
|
||||
auto block_end = block_size + m_pending_block_size;
|
||||
size_t current_position;
|
||||
for (current_position = block_size; current_position < block_end - min_match_length + 1; current_position++) {
|
||||
auto hash = hash_sequence(&m_rolling_window[current_position]);
|
||||
size_t match_position;
|
||||
auto match_length = find_back_match(current_position, hash, previous_match_length,
|
||||
min(m_compression_constants.great_match_length, block_end - current_position), match_position);
|
||||
|
||||
insert_hash(current_position, hash);
|
||||
|
||||
// if the previous match is as good as the new match, just use it
|
||||
if (previous_match_length != 0 && previous_match_length >= match_length) {
|
||||
emit_back_reference((current_position - 1) - previous_match_position, previous_match_length);
|
||||
|
||||
// skip all the bytes that are included in this match
|
||||
for (size_t j = current_position + 1; j < min(current_position - 1 + previous_match_length, block_end - min_match_length + 1); j++) {
|
||||
insert_hash(j, hash_sequence(&m_rolling_window[j]));
|
||||
}
|
||||
current_position = (current_position - 1) + previous_match_length - 1;
|
||||
previous_match_length = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (match_length == 0) {
|
||||
VERIFY(previous_match_length == 0);
|
||||
emit_literal(m_rolling_window[current_position]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is a lazy match, and the new match is better than the old one, output previous as literal
|
||||
if (previous_match_length != 0) {
|
||||
emit_literal(m_rolling_window[current_position - 1]);
|
||||
}
|
||||
|
||||
previous_match_length = match_length;
|
||||
previous_match_position = match_position;
|
||||
}
|
||||
|
||||
// clean up leftover lazy match
|
||||
if (previous_match_length != 0) {
|
||||
emit_back_reference((current_position - 1) - previous_match_position, previous_match_length);
|
||||
current_position = (current_position - 1) + previous_match_length;
|
||||
}
|
||||
|
||||
// output remaining literals
|
||||
while (current_position < block_end) {
|
||||
emit_literal(m_rolling_window[current_position++]);
|
||||
}
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::huffman_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
for (size_t i = 0; i < 286; i++) {
|
||||
auto frequency = m_symbol_frequencies[i];
|
||||
length += literal_bit_lengths[i] * frequency;
|
||||
|
||||
if (i >= 257) // back reference length symbols
|
||||
length += packed_length_symbols[i - 257].extra_bits * frequency;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 30; i++) {
|
||||
auto frequency = m_distance_frequencies[i];
|
||||
length += distance_bit_lengths[i] * frequency;
|
||||
length += packed_distances[i].extra_bits * frequency;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::uncompressed_block_length()
|
||||
{
|
||||
auto padding = 8 - ((m_output_stream->bit_offset() + 3) % 8);
|
||||
// 3 bit block header + align to byte + 2 * 16 bit length fields + block contents
|
||||
return 3 + padding + (2 * 16) + m_pending_block_size * 8;
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::fixed_block_length()
|
||||
{
|
||||
// block header + fixed huffman encoded block contents
|
||||
return 3 + huffman_block_length(fixed_literal_bit_lengths, fixed_distance_bit_lengths);
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::dynamic_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<u8, 19> const& code_lengths_bit_lengths, Array<u16, 19> const& code_lengths_frequencies, size_t code_lengths_count)
|
||||
{
|
||||
// block header + literal code count + distance code count + code length count
|
||||
auto length = 3 + 5 + 5 + 4;
|
||||
|
||||
// 3 bits per code_length
|
||||
length += 3 * code_lengths_count;
|
||||
|
||||
for (size_t i = 0; i < code_lengths_frequencies.size(); i++) {
|
||||
auto frequency = code_lengths_frequencies[i];
|
||||
length += code_lengths_bit_lengths[i] * frequency;
|
||||
|
||||
if (i == deflate_special_code_length_copy) {
|
||||
length += 2 * frequency;
|
||||
} else if (i == deflate_special_code_length_zeros) {
|
||||
length += 3 * frequency;
|
||||
} else if (i == deflate_special_code_length_long_zeros) {
|
||||
length += 7 * frequency;
|
||||
}
|
||||
}
|
||||
|
||||
return length + huffman_block_length(literal_bit_lengths, distance_bit_lengths);
|
||||
}
|
||||
|
||||
ErrorOr<void> DeflateCompressor::write_huffman(CanonicalCode const& literal_code, Optional<CanonicalCode> const& distance_code)
|
||||
{
|
||||
auto has_distances = distance_code.has_value();
|
||||
for (size_t i = 0; i < m_pending_symbol_size; i++) {
|
||||
if (m_symbol_buffer[i].distance == 0) {
|
||||
TRY(literal_code.write_symbol(*m_output_stream, m_symbol_buffer[i].literal));
|
||||
continue;
|
||||
}
|
||||
VERIFY(has_distances);
|
||||
auto symbol = length_to_symbol[m_symbol_buffer[i].length];
|
||||
TRY(literal_code.write_symbol(*m_output_stream, symbol));
|
||||
// Emit extra bits if needed
|
||||
TRY(m_output_stream->write_bits<u16>(m_symbol_buffer[i].length - packed_length_symbols[symbol - 257].base_length, packed_length_symbols[symbol - 257].extra_bits));
|
||||
|
||||
auto base_distance = distance_to_base(m_symbol_buffer[i].distance);
|
||||
TRY(distance_code.value().write_symbol(*m_output_stream, base_distance));
|
||||
// Emit extra bits if needed
|
||||
TRY(m_output_stream->write_bits<u16>(m_symbol_buffer[i].distance - packed_distances[base_distance].base_distance, packed_distances[base_distance].extra_bits));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::encode_huffman_lengths(ReadonlyBytes lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths)
|
||||
{
|
||||
size_t encoded_count = 0;
|
||||
size_t i = 0;
|
||||
while (i < lengths.size()) {
|
||||
if (lengths[i] == 0) {
|
||||
auto zero_count = 0;
|
||||
for (size_t j = i; j < min(lengths.size(), i + 138) && lengths[j] == 0; j++)
|
||||
zero_count++;
|
||||
|
||||
if (zero_count < 3) { // below minimum repeated zero count
|
||||
encoded_lengths[encoded_count++].symbol = 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zero_count <= 10) {
|
||||
encoded_lengths[encoded_count].symbol = deflate_special_code_length_zeros;
|
||||
encoded_lengths[encoded_count++].count = zero_count;
|
||||
} else {
|
||||
encoded_lengths[encoded_count].symbol = deflate_special_code_length_long_zeros;
|
||||
encoded_lengths[encoded_count++].count = zero_count;
|
||||
}
|
||||
i += zero_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
encoded_lengths[encoded_count++].symbol = lengths[i++];
|
||||
|
||||
auto copy_count = 0;
|
||||
for (size_t j = i; j < min(lengths.size(), i + 6) && lengths[j] == lengths[i - 1]; j++)
|
||||
copy_count++;
|
||||
|
||||
if (copy_count >= 3) {
|
||||
encoded_lengths[encoded_count].symbol = deflate_special_code_length_copy;
|
||||
encoded_lengths[encoded_count++].count = copy_count;
|
||||
i += copy_count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return encoded_count;
|
||||
}
|
||||
|
||||
size_t DeflateCompressor::encode_block_lengths(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count)
|
||||
{
|
||||
literal_code_count = max_huffman_literals;
|
||||
distance_code_count = max_huffman_distances;
|
||||
|
||||
VERIFY(literal_bit_lengths[EndOfBlock] != 0); // Make sure at least the EndOfBlock marker is present
|
||||
while (literal_bit_lengths[literal_code_count - 1] == 0)
|
||||
literal_code_count--;
|
||||
|
||||
// Drop trailing zero lengths, keeping at least one
|
||||
while (distance_bit_lengths[distance_code_count - 1] == 0 && distance_code_count > 1)
|
||||
distance_code_count--;
|
||||
|
||||
Array<u8, max_huffman_literals + max_huffman_distances> all_lengths {};
|
||||
for (size_t i = 0; i < literal_code_count; i++)
|
||||
all_lengths[i] = literal_bit_lengths[i];
|
||||
for (size_t i = 0; i < distance_code_count; i++)
|
||||
all_lengths[literal_code_count + i] = distance_bit_lengths[i];
|
||||
|
||||
return encode_huffman_lengths(all_lengths.span().trim(literal_code_count + distance_code_count), encoded_lengths);
|
||||
}
|
||||
|
||||
ErrorOr<void> DeflateCompressor::write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional<CanonicalCode> const& distance_code, size_t distance_code_count, Array<u8, 19> const& code_lengths_bit_lengths, size_t code_length_count, Array<code_length_symbol, max_huffman_literals + max_huffman_distances> const& encoded_lengths, size_t encoded_lengths_count)
|
||||
{
|
||||
TRY(m_output_stream->write_bits(literal_code_count - 257, 5));
|
||||
TRY(m_output_stream->write_bits(distance_code_count - 1, 5));
|
||||
TRY(m_output_stream->write_bits(code_length_count - 4, 4));
|
||||
|
||||
for (size_t i = 0; i < code_length_count; i++) {
|
||||
TRY(m_output_stream->write_bits(code_lengths_bit_lengths[code_lengths_code_lengths_order[i]], 3));
|
||||
}
|
||||
|
||||
auto code_lengths_code = MUST(CanonicalCode::from_bytes(code_lengths_bit_lengths));
|
||||
for (size_t i = 0; i < encoded_lengths_count; i++) {
|
||||
auto encoded_length = encoded_lengths[i];
|
||||
TRY(code_lengths_code.write_symbol(*m_output_stream, encoded_length.symbol));
|
||||
if (encoded_length.symbol == deflate_special_code_length_copy) {
|
||||
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 3, 2));
|
||||
} else if (encoded_length.symbol == deflate_special_code_length_zeros) {
|
||||
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 3, 3));
|
||||
} else if (encoded_length.symbol == deflate_special_code_length_long_zeros) {
|
||||
TRY(m_output_stream->write_bits<u8>(encoded_length.count - 11, 7));
|
||||
}
|
||||
}
|
||||
|
||||
TRY(write_huffman(literal_code, distance_code));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> DeflateCompressor::flush()
|
||||
{
|
||||
TRY(m_output_stream->write_bits(m_finished, 1));
|
||||
|
||||
// if this is just an empty block to signify the end of the deflate stream use the smallest block possible (10 bits total)
|
||||
if (m_pending_block_size == 0) {
|
||||
VERIFY(m_finished); // we shouldn't be writing empty blocks unless this is the final one
|
||||
TRY(m_output_stream->write_bits(0b01u, 2)); // fixed huffman codes
|
||||
TRY(m_output_stream->write_bits(0b0000000u, 7)); // end of block symbol
|
||||
TRY(m_output_stream->align_to_byte_boundary());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto write_uncompressed = [&]() -> ErrorOr<void> {
|
||||
TRY(m_output_stream->write_bits(0b00u, 2)); // no compression
|
||||
TRY(m_output_stream->align_to_byte_boundary());
|
||||
TRY(m_output_stream->write_value<LittleEndian<u16>>(m_pending_block_size));
|
||||
TRY(m_output_stream->write_value<LittleEndian<u16>>(~m_pending_block_size));
|
||||
TRY(m_output_stream->write_until_depleted(pending_block().slice(0, m_pending_block_size)));
|
||||
return {};
|
||||
};
|
||||
|
||||
if (m_compression_level == CompressionLevel::STORE) { // disabled compression fast path
|
||||
TRY(write_uncompressed());
|
||||
m_pending_block_size = 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
// The following implementation of lz77 compression and huffman encoding is based on the reference implementation by Hans Wennborg https://www.hanshq.net/zip.html
|
||||
|
||||
// this reads from the pending block and writes to m_symbol_buffer
|
||||
lz77_compress_block();
|
||||
|
||||
// insert EndOfBlock marker to the symbol buffer
|
||||
m_symbol_buffer[m_pending_symbol_size].distance = 0;
|
||||
m_symbol_buffer[m_pending_symbol_size++].literal = EndOfBlock;
|
||||
m_symbol_frequencies[EndOfBlock]++;
|
||||
|
||||
// generate optimal dynamic huffman code lengths
|
||||
Array<u8, max_huffman_literals> dynamic_literal_bit_lengths {};
|
||||
Array<u8, max_huffman_distances> dynamic_distance_bit_lengths {};
|
||||
generate_huffman_lengths(dynamic_literal_bit_lengths, m_symbol_frequencies, 15); // deflate data huffman can use up to 15 bits per symbol
|
||||
generate_huffman_lengths(dynamic_distance_bit_lengths, m_distance_frequencies, 15);
|
||||
|
||||
// encode literal and distance lengths together in deflate format
|
||||
Array<code_length_symbol, max_huffman_literals + max_huffman_distances> encoded_lengths {};
|
||||
size_t literal_code_count;
|
||||
size_t distance_code_count;
|
||||
auto encoded_lengths_count = encode_block_lengths(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, encoded_lengths, literal_code_count, distance_code_count);
|
||||
|
||||
// count code length frequencies
|
||||
Array<u16, 19> code_lengths_frequencies { 0 };
|
||||
for (size_t i = 0; i < encoded_lengths_count; i++) {
|
||||
code_lengths_frequencies[encoded_lengths[i].symbol]++;
|
||||
}
|
||||
// generate optimal huffman code lengths code lengths
|
||||
Array<u8, 19> code_lengths_bit_lengths {};
|
||||
generate_huffman_lengths(code_lengths_bit_lengths, code_lengths_frequencies, 7); // deflate code length huffman can use up to 7 bits per symbol
|
||||
// calculate actual code length code lengths count (without trailing zeros)
|
||||
auto code_lengths_count = code_lengths_bit_lengths.size();
|
||||
while (code_lengths_bit_lengths[code_lengths_code_lengths_order[code_lengths_count - 1]] == 0)
|
||||
code_lengths_count--;
|
||||
|
||||
auto uncompressed_size = uncompressed_block_length();
|
||||
auto fixed_huffman_size = fixed_block_length();
|
||||
auto dynamic_huffman_size = dynamic_block_length(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, code_lengths_bit_lengths, code_lengths_frequencies, code_lengths_count);
|
||||
|
||||
// If the compression somehow didn't reduce the size enough, just write out the block uncompressed as it allows for much faster decompression
|
||||
if (uncompressed_size <= min(fixed_huffman_size, dynamic_huffman_size)) {
|
||||
TRY(write_uncompressed());
|
||||
} else if (fixed_huffman_size <= dynamic_huffman_size) {
|
||||
// If the fixed and dynamic huffman codes come out the same size, prefer the fixed version, as it takes less time to decode fixed huffman codes.
|
||||
TRY(m_output_stream->write_bits(0b01u, 2));
|
||||
TRY(write_huffman(CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes()));
|
||||
} else {
|
||||
// dynamic huffman codes
|
||||
TRY(m_output_stream->write_bits(0b10u, 2));
|
||||
auto literal_code = MUST(CanonicalCode::from_bytes(dynamic_literal_bit_lengths));
|
||||
auto distance_code_or_error = CanonicalCode::from_bytes(dynamic_distance_bit_lengths);
|
||||
Optional<CanonicalCode> distance_code;
|
||||
if (!distance_code_or_error.is_error())
|
||||
distance_code = distance_code_or_error.release_value();
|
||||
TRY(write_dynamic_huffman(literal_code, literal_code_count, distance_code, distance_code_count, code_lengths_bit_lengths, code_lengths_count, encoded_lengths, encoded_lengths_count));
|
||||
}
|
||||
if (m_finished)
|
||||
TRY(m_output_stream->align_to_byte_boundary());
|
||||
|
||||
// reset all block specific members
|
||||
m_pending_block_size = 0;
|
||||
m_pending_symbol_size = 0;
|
||||
m_symbol_frequencies.fill(0);
|
||||
m_distance_frequencies.fill(0);
|
||||
// On the final block this copy will potentially produce an invalid search window, but since its the final block we dont care
|
||||
pending_block().copy_trimmed_to({ m_rolling_window, block_size });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> DeflateCompressor::final_flush()
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
m_finished = true;
|
||||
TRY(flush());
|
||||
TRY(m_output_stream->flush_buffer_to_stream());
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> DeflateCompressor::compress_all(ReadonlyBytes bytes, CompressionLevel compression_level)
|
||||
{
|
||||
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
|
||||
auto deflate_stream = TRY(DeflateCompressor::construct(MaybeOwned<Stream>(*output_stream), compression_level));
|
||||
|
||||
TRY(deflate_stream->write_until_depleted(bytes));
|
||||
TRY(deflate_stream->final_flush());
|
||||
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
|
||||
TRY(output_stream->read_until_filled(buffer));
|
||||
|
||||
return buffer;
|
||||
return ::Compress::compress_all<DeflateCompressor>(bytes, compression_level);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -8,14 +9,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/CircularBuffer.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCompress/DeflateTables.h>
|
||||
#include <LibCompress/Zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
|
@ -60,172 +55,28 @@ ALWAYS_INLINE ErrorOr<void> CanonicalCode::write_symbol(LittleEndianOutputBitStr
|
|||
return {};
|
||||
}
|
||||
|
||||
class DeflateDecompressor final : public Stream {
|
||||
private:
|
||||
class CompressedBlock {
|
||||
public:
|
||||
CompressedBlock(DeflateDecompressor&, CanonicalCode literal_codes, Optional<CanonicalCode> distance_codes);
|
||||
|
||||
ErrorOr<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);
|
||||
|
||||
ErrorOr<bool> try_read_more();
|
||||
|
||||
private:
|
||||
DeflateDecompressor& m_decompressor;
|
||||
size_t m_bytes_remaining;
|
||||
};
|
||||
|
||||
enum class State {
|
||||
Idle,
|
||||
ReadingCompressedBlock,
|
||||
ReadingUncompressedBlock
|
||||
};
|
||||
|
||||
class DeflateDecompressor final : public GenericZlibDecompressor {
|
||||
public:
|
||||
friend CompressedBlock;
|
||||
friend UncompressedBlock;
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<DeflateDecompressor>> construct(MaybeOwned<LittleEndianInputBitStream> stream);
|
||||
~DeflateDecompressor();
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<DeflateDecompressor>> create(MaybeOwned<Stream>);
|
||||
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
|
||||
|
||||
private:
|
||||
DeflateDecompressor(MaybeOwned<LittleEndianInputBitStream> stream, CircularBuffer buffer);
|
||||
|
||||
ErrorOr<u32> decode_length(u32);
|
||||
ErrorOr<u32> decode_distance(u32);
|
||||
ErrorOr<void> decode_codes(CanonicalCode& literal_code, Optional<CanonicalCode>& distance_code);
|
||||
|
||||
static constexpr u16 max_back_reference_length = 258;
|
||||
|
||||
bool m_read_final_block { false };
|
||||
|
||||
State m_state { State::Idle };
|
||||
union {
|
||||
CompressedBlock m_compressed_block;
|
||||
UncompressedBlock m_uncompressed_block;
|
||||
};
|
||||
|
||||
MaybeOwned<LittleEndianInputBitStream> m_input_stream;
|
||||
CircularBuffer m_output_buffer;
|
||||
DeflateDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class DeflateCompressor final : public Stream {
|
||||
class DeflateCompressor final : public GenericZlibCompressor {
|
||||
public:
|
||||
static constexpr size_t block_size = 32 * KiB - 1; // TODO: this can theoretically be increased to 64 KiB - 2
|
||||
static constexpr size_t window_size = block_size * 2;
|
||||
static constexpr size_t hash_bits = 15;
|
||||
static constexpr size_t max_huffman_literals = 288;
|
||||
static constexpr size_t max_huffman_distances = 32;
|
||||
static constexpr size_t min_match_length = 4; // matches smaller than these are not worth the size of the back reference
|
||||
static constexpr size_t max_match_length = 258; // matches longer than these cannot be encoded using huffman codes
|
||||
static constexpr u16 empty_slot = UINT16_MAX;
|
||||
|
||||
struct CompressionConstants {
|
||||
size_t good_match_length; // Once we find a match of at least this length (a good enough match) we reduce max_chain to lower processing time
|
||||
size_t max_lazy_length; // If the match is at least this long we dont defer matching to the next byte (which takes time) as its good enough
|
||||
size_t great_match_length; // Once we find a match of at least this length (a great match) we can just stop searching for longer ones
|
||||
size_t max_chain; // We only check the actual length of the max_chain closest matches
|
||||
};
|
||||
|
||||
// These constants were shamelessly "borrowed" from zlib
|
||||
static constexpr CompressionConstants compression_constants[] = {
|
||||
{ 0, 0, 0, 0 },
|
||||
{ 4, 4, 8, 4 },
|
||||
{ 8, 16, 128, 128 },
|
||||
{ 32, 258, 258, 4096 },
|
||||
{ max_match_length, max_match_length, max_match_length, 1 << hash_bits } // disable all limits
|
||||
};
|
||||
|
||||
enum class CompressionLevel : int {
|
||||
STORE = 0,
|
||||
FAST,
|
||||
GOOD,
|
||||
GREAT,
|
||||
BEST // WARNING: this one can take an unreasonable amount of time!
|
||||
};
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<DeflateCompressor>> construct(MaybeOwned<Stream>, CompressionLevel = CompressionLevel::GOOD);
|
||||
~DeflateCompressor();
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
ErrorOr<void> final_flush();
|
||||
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, CompressionLevel = CompressionLevel::GOOD);
|
||||
static ErrorOr<NonnullOwnPtr<DeflateCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
|
||||
private:
|
||||
DeflateCompressor(NonnullOwnPtr<LittleEndianOutputBitStream>, CompressionLevel = CompressionLevel::GOOD);
|
||||
|
||||
Bytes pending_block() { return { m_rolling_window + block_size, block_size }; }
|
||||
|
||||
// LZ77 Compression
|
||||
static u16 hash_sequence(u8 const* bytes);
|
||||
size_t compare_match_candidate(size_t start, size_t candidate, size_t prev_match_length, size_t max_match_length);
|
||||
size_t find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t max_match_length, size_t& match_position);
|
||||
void lz77_compress_block();
|
||||
|
||||
// Huffman Coding
|
||||
struct code_length_symbol {
|
||||
u8 symbol;
|
||||
u8 count; // used for special symbols 16-18
|
||||
};
|
||||
static u8 distance_to_base(u16 distance);
|
||||
size_t huffman_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths);
|
||||
ErrorOr<void> write_huffman(CanonicalCode const& literal_code, Optional<CanonicalCode> const& distance_code);
|
||||
static size_t encode_huffman_lengths(ReadonlyBytes lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths);
|
||||
size_t encode_block_lengths(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<code_length_symbol, max_huffman_literals + max_huffman_distances>& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count);
|
||||
ErrorOr<void> write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional<CanonicalCode> const& distance_code, size_t distance_code_count, Array<u8, 19> const& code_lengths_bit_lengths, size_t code_length_count, Array<code_length_symbol, max_huffman_literals + max_huffman_distances> const& encoded_lengths, size_t encoded_lengths_count);
|
||||
|
||||
size_t uncompressed_block_length();
|
||||
size_t fixed_block_length();
|
||||
size_t dynamic_block_length(Array<u8, max_huffman_literals> const& literal_bit_lengths, Array<u8, max_huffman_distances> const& distance_bit_lengths, Array<u8, 19> const& code_lengths_bit_lengths, Array<u16, 19> const& code_lengths_frequencies, size_t code_lengths_count);
|
||||
ErrorOr<void> flush();
|
||||
|
||||
bool m_finished { false };
|
||||
CompressionLevel m_compression_level;
|
||||
CompressionConstants m_compression_constants;
|
||||
NonnullOwnPtr<LittleEndianOutputBitStream> m_output_stream;
|
||||
|
||||
u8 m_rolling_window[window_size];
|
||||
size_t m_pending_block_size { 0 };
|
||||
|
||||
struct [[gnu::packed]] {
|
||||
u16 distance; // back reference length
|
||||
union {
|
||||
u16 literal; // literal byte or on of block symbol
|
||||
u16 length; // back reference length (if distance != 0)
|
||||
};
|
||||
} m_symbol_buffer[block_size + 1];
|
||||
size_t m_pending_symbol_size { 0 };
|
||||
Array<u16, max_huffman_literals> m_symbol_frequencies; // there are 286 valid symbol values (symbols 286-287 never occur)
|
||||
Array<u16, max_huffman_distances> m_distance_frequencies; // there are 30 valid distance values (distances 30-31 never occur)
|
||||
|
||||
// LZ77 Chained hash table
|
||||
u16 m_hash_head[1 << hash_bits];
|
||||
u16 m_hash_prev[window_size];
|
||||
DeflateCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibCompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
219
Libraries/LibCompress/GenericZlib.cpp
Normal file
219
Libraries/LibCompress/GenericZlib.cpp
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCompress/GenericZlib.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
static Error handle_zlib_error(int ret)
|
||||
{
|
||||
switch (ret) {
|
||||
case Z_ERRNO:
|
||||
return Error::from_errno(errno);
|
||||
case Z_DATA_ERROR:
|
||||
// Z_DATA_ERROR if the input data was corrupted
|
||||
return Error::from_string_literal("zlib data error");
|
||||
case Z_STREAM_ERROR:
|
||||
// Z_STREAM_ERROR if the parameters are invalid, such as a null pointer to the structure
|
||||
return Error::from_string_literal("zlib stream error");
|
||||
case Z_VERSION_ERROR:
|
||||
// Z_VERSION_ERROR if the zlib library version is incompatible with the version assumed by the caller
|
||||
return Error::from_string_literal("zlib version mismatch");
|
||||
case Z_MEM_ERROR:
|
||||
// Z_MEM_ERROR if there was not enough memory
|
||||
return Error::from_errno(ENOMEM);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
GenericZlibDecompressor::GenericZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: m_stream(move(stream))
|
||||
, m_zstream(zstream)
|
||||
, m_buffer(move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<z_stream*> GenericZlibDecompressor::new_z_stream(int window_bits)
|
||||
{
|
||||
auto zstream = new (nothrow) z_stream {};
|
||||
if (!zstream)
|
||||
return Error::from_errno(ENOMEM);
|
||||
|
||||
// The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller.
|
||||
zstream->next_in = nullptr;
|
||||
zstream->avail_in = 0;
|
||||
zstream->zalloc = nullptr;
|
||||
zstream->zfree = nullptr;
|
||||
zstream->opaque = nullptr;
|
||||
|
||||
if (auto ret = inflateInit2(zstream, window_bits); ret != Z_OK)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
return zstream;
|
||||
}
|
||||
|
||||
GenericZlibDecompressor::~GenericZlibDecompressor()
|
||||
{
|
||||
inflateEnd(m_zstream);
|
||||
delete m_zstream;
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GenericZlibDecompressor::read_some(Bytes bytes)
|
||||
{
|
||||
m_zstream->avail_out = bytes.size();
|
||||
m_zstream->next_out = bytes.data();
|
||||
|
||||
if (m_zstream->avail_in == 0) {
|
||||
auto in = TRY(m_stream->read_some(m_buffer.span()));
|
||||
m_zstream->avail_in = in.size();
|
||||
m_zstream->next_in = m_buffer.data();
|
||||
}
|
||||
|
||||
auto ret = inflate(m_zstream, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
if (ret == Z_STREAM_END) {
|
||||
inflateReset(m_zstream);
|
||||
if (m_zstream->avail_in == 0)
|
||||
m_eof = true;
|
||||
}
|
||||
|
||||
return bytes.slice(0, bytes.size() - m_zstream->avail_out);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> GenericZlibDecompressor::write_some(ReadonlyBytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
bool GenericZlibDecompressor::is_eof() const
|
||||
{
|
||||
return m_eof;
|
||||
}
|
||||
|
||||
bool GenericZlibDecompressor::is_open() const
|
||||
{
|
||||
return m_stream->is_open();
|
||||
}
|
||||
|
||||
void GenericZlibDecompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
GenericZlibCompressor::GenericZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: m_stream(move(stream))
|
||||
, m_zstream(zstream)
|
||||
, m_buffer(move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<z_stream*> GenericZlibCompressor::new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
auto zstream = new (nothrow) z_stream {};
|
||||
if (!zstream)
|
||||
return Error::from_errno(ENOMEM);
|
||||
|
||||
// The fields zalloc, zfree and opaque must be initialized before by the caller.
|
||||
zstream->zalloc = nullptr;
|
||||
zstream->zfree = nullptr;
|
||||
zstream->opaque = nullptr;
|
||||
|
||||
int level = [&] {
|
||||
switch (compression_level) {
|
||||
case GenericZlibCompressionLevel::Fastest:
|
||||
return Z_BEST_SPEED;
|
||||
case GenericZlibCompressionLevel::Default:
|
||||
return Z_DEFAULT_COMPRESSION;
|
||||
case GenericZlibCompressionLevel::Best:
|
||||
return Z_BEST_COMPRESSION;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
|
||||
if (auto ret = deflateInit2(zstream, level, Z_DEFLATED, window_bits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); ret != Z_OK)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
return zstream;
|
||||
}
|
||||
|
||||
GenericZlibCompressor::~GenericZlibCompressor()
|
||||
{
|
||||
deflateEnd(m_zstream);
|
||||
delete m_zstream;
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GenericZlibCompressor::read_some(Bytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> GenericZlibCompressor::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
m_zstream->avail_in = bytes.size();
|
||||
m_zstream->next_in = const_cast<u8*>(bytes.data());
|
||||
|
||||
// If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter
|
||||
// and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out).
|
||||
do {
|
||||
m_zstream->avail_out = m_buffer.size();
|
||||
m_zstream->next_out = m_buffer.data();
|
||||
|
||||
auto ret = deflate(m_zstream, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_BUF_ERROR)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
auto have = m_buffer.size() - m_zstream->avail_out;
|
||||
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
|
||||
} while (m_zstream->avail_out == 0);
|
||||
|
||||
VERIFY(m_zstream->avail_in == 0);
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
bool GenericZlibCompressor::is_eof() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GenericZlibCompressor::is_open() const
|
||||
{
|
||||
return m_stream->is_open();
|
||||
}
|
||||
|
||||
void GenericZlibCompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> GenericZlibCompressor::finish()
|
||||
{
|
||||
VERIFY(m_zstream->avail_in == 0);
|
||||
|
||||
// If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END
|
||||
// if there was enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH
|
||||
// and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error.
|
||||
while (true) {
|
||||
m_zstream->avail_out = m_buffer.size();
|
||||
m_zstream->next_out = m_buffer.data();
|
||||
|
||||
auto ret = deflate(m_zstream, Z_FINISH);
|
||||
if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || ret == Z_OK) {
|
||||
auto have = m_buffer.size() - m_zstream->avail_out;
|
||||
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
|
||||
|
||||
if (ret == Z_STREAM_END)
|
||||
return {};
|
||||
} else {
|
||||
return handle_zlib_error(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
101
Libraries/LibCompress/GenericZlib.h
Normal file
101
Libraries/LibCompress/GenericZlib.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Stream.h>
|
||||
|
||||
extern "C" {
|
||||
typedef struct z_stream_s z_stream;
|
||||
}
|
||||
|
||||
namespace Compress {
|
||||
|
||||
enum class GenericZlibCompressionLevel : u8 {
|
||||
Fastest,
|
||||
Default,
|
||||
Best,
|
||||
};
|
||||
|
||||
class GenericZlibDecompressor : public Stream {
|
||||
AK_MAKE_NONCOPYABLE(GenericZlibDecompressor);
|
||||
|
||||
public:
|
||||
~GenericZlibDecompressor() override;
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
|
||||
protected:
|
||||
GenericZlibDecompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
|
||||
|
||||
static ErrorOr<z_stream*> new_z_stream(int window_bits);
|
||||
|
||||
private:
|
||||
MaybeOwned<Stream> m_stream;
|
||||
z_stream* m_zstream;
|
||||
|
||||
bool m_eof { false };
|
||||
|
||||
AK::FixedArray<u8> m_buffer;
|
||||
};
|
||||
|
||||
class GenericZlibCompressor : public Stream {
|
||||
AK_MAKE_NONCOPYABLE(GenericZlibCompressor);
|
||||
|
||||
public:
|
||||
~GenericZlibCompressor() override;
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
ErrorOr<void> finish();
|
||||
|
||||
protected:
|
||||
GenericZlibCompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
|
||||
|
||||
static ErrorOr<z_stream*> new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level);
|
||||
|
||||
private:
|
||||
MaybeOwned<Stream> m_stream;
|
||||
z_stream* m_zstream;
|
||||
|
||||
AK::FixedArray<u8> m_buffer;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
auto input_stream = make<AK::FixedMemoryStream>(bytes);
|
||||
auto deflate_stream = TRY(T::create(MaybeOwned<Stream>(move(input_stream))));
|
||||
return TRY(deflate_stream->read_until_eof(4096));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
|
||||
auto gzip_stream = TRY(T::create(MaybeOwned { *output_stream }, compression_level));
|
||||
|
||||
TRY(gzip_stream->write_until_depleted(bytes));
|
||||
TRY(gzip_stream->finish());
|
||||
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
|
||||
TRY(output_stream->read_until_filled(buffer.bytes()));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,267 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCompress/Gzip.h>
|
||||
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/DateTime.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
bool GzipDecompressor::is_likely_compressed(ReadonlyBytes bytes)
|
||||
ErrorOr<NonnullOwnPtr<GzipDecompressor>> GzipDecompressor::create(MaybeOwned<Stream> stream)
|
||||
{
|
||||
return bytes.size() >= 2 && bytes[0] == gzip_magic_1 && bytes[1] == gzip_magic_2;
|
||||
}
|
||||
|
||||
bool BlockHeader::valid_magic_number() const
|
||||
{
|
||||
return identification_1 == gzip_magic_1 && identification_2 == gzip_magic_2;
|
||||
}
|
||||
|
||||
bool 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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<GzipDecompressor::Member>> GzipDecompressor::Member::construct(BlockHeader header, LittleEndianInputBitStream& stream)
|
||||
{
|
||||
auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned<LittleEndianInputBitStream>(stream)));
|
||||
return TRY(adopt_nonnull_own_or_enomem(new (nothrow) Member(header, move(deflate_stream))));
|
||||
}
|
||||
|
||||
GzipDecompressor::Member::Member(BlockHeader header, NonnullOwnPtr<DeflateDecompressor> stream)
|
||||
: m_header(header)
|
||||
, m_stream(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
GzipDecompressor::GzipDecompressor(MaybeOwned<Stream> stream)
|
||||
: m_input_stream(make<LittleEndianInputBitStream>(move(stream)))
|
||||
{
|
||||
}
|
||||
|
||||
GzipDecompressor::~GzipDecompressor()
|
||||
{
|
||||
m_current_member.clear();
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GzipDecompressor::read_some(Bytes bytes)
|
||||
{
|
||||
size_t total_read = 0;
|
||||
while (total_read < bytes.size()) {
|
||||
if (is_eof())
|
||||
break;
|
||||
|
||||
auto slice = bytes.slice(total_read);
|
||||
|
||||
if (m_current_member) {
|
||||
auto current_slice = TRY(current_member().m_stream->read_some(slice));
|
||||
current_member().m_checksum.update(current_slice);
|
||||
current_member().m_nread += current_slice.size();
|
||||
|
||||
if (current_slice.size() < slice.size()) {
|
||||
u32 crc32 = TRY(m_input_stream->read_value<LittleEndian<u32>>());
|
||||
u32 input_size = TRY(m_input_stream->read_value<LittleEndian<u32>>());
|
||||
|
||||
if (crc32 != current_member().m_checksum.digest())
|
||||
return Error::from_string_literal("Stored CRC32 does not match the calculated CRC32 of the current member");
|
||||
|
||||
if (input_size != current_member().m_nread)
|
||||
return Error::from_string_literal("Input size does not match the number of read bytes");
|
||||
|
||||
m_current_member.clear();
|
||||
|
||||
total_read += current_slice.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
total_read += current_slice.size();
|
||||
continue;
|
||||
} else {
|
||||
auto current_partial_header_slice = Bytes { m_partial_header, sizeof(BlockHeader) }.slice(m_partial_header_offset);
|
||||
auto current_partial_header_data = TRY(m_input_stream->read_some(current_partial_header_slice));
|
||||
m_partial_header_offset += current_partial_header_data.size();
|
||||
|
||||
if (is_eof())
|
||||
break;
|
||||
|
||||
if (m_partial_header_offset < sizeof(BlockHeader)) {
|
||||
break; // partial header read
|
||||
}
|
||||
m_partial_header_offset = 0;
|
||||
|
||||
BlockHeader header = *(reinterpret_cast<BlockHeader*>(m_partial_header));
|
||||
|
||||
if (!header.valid_magic_number())
|
||||
return Error::from_string_literal("Header does not have a valid magic number");
|
||||
|
||||
if (!header.supported_by_implementation())
|
||||
return Error::from_string_literal("Header is not supported by implementation");
|
||||
|
||||
if (header.flags & Flags::FEXTRA) {
|
||||
u16 subfield_id = TRY(m_input_stream->read_value<LittleEndian<u16>>());
|
||||
u16 length = TRY(m_input_stream->read_value<LittleEndian<u16>>());
|
||||
TRY(m_input_stream->discard(length));
|
||||
(void)subfield_id;
|
||||
}
|
||||
|
||||
auto discard_string = [&]() -> ErrorOr<void> {
|
||||
char next_char;
|
||||
do {
|
||||
next_char = TRY(m_input_stream->read_value<char>());
|
||||
} while (next_char);
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
if (header.flags & Flags::FNAME)
|
||||
TRY(discard_string());
|
||||
|
||||
if (header.flags & Flags::FCOMMENT)
|
||||
TRY(discard_string());
|
||||
|
||||
if (header.flags & Flags::FHCRC) {
|
||||
u16 crc = TRY(m_input_stream->read_value<LittleEndian<u16>>());
|
||||
// FIXME: we should probably verify this instead of just assuming it matches
|
||||
(void)crc;
|
||||
}
|
||||
|
||||
m_current_member = TRY(Member::construct(header, *m_input_stream));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return bytes.slice(0, total_read);
|
||||
}
|
||||
|
||||
ErrorOr<Optional<String>> GzipDecompressor::describe_header(ReadonlyBytes bytes)
|
||||
{
|
||||
if (bytes.size() < sizeof(BlockHeader))
|
||||
return OptionalNone {};
|
||||
|
||||
auto& header = *(reinterpret_cast<BlockHeader const*>(bytes.data()));
|
||||
if (!header.valid_magic_number() || !header.supported_by_implementation())
|
||||
return OptionalNone {};
|
||||
|
||||
LittleEndian<u32> original_size = *reinterpret_cast<u32 const*>(bytes.offset(bytes.size() - sizeof(u32)));
|
||||
return TRY(String::formatted("last modified: {}, original size {}", Core::DateTime::from_timestamp(header.modification_time), (u32)original_size));
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS | 16));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) GzipDecompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> GzipDecompressor::decompress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
auto memory_stream = TRY(try_make<FixedMemoryStream>(bytes));
|
||||
auto gzip_stream = make<GzipDecompressor>(move(memory_stream));
|
||||
AllocatingMemoryStream output_stream;
|
||||
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
|
||||
while (!gzip_stream->is_eof()) {
|
||||
auto const data = TRY(gzip_stream->read_some(buffer));
|
||||
TRY(output_stream.write_until_depleted(data));
|
||||
}
|
||||
|
||||
auto output_buffer = TRY(ByteBuffer::create_uninitialized(output_stream.used_buffer_size()));
|
||||
TRY(output_stream.read_until_filled(output_buffer));
|
||||
return output_buffer;
|
||||
return ::Compress::decompress_all<GzipDecompressor>(bytes);
|
||||
}
|
||||
|
||||
bool GzipDecompressor::is_eof() const { return m_input_stream->is_eof(); }
|
||||
|
||||
ErrorOr<size_t> GzipDecompressor::write_some(ReadonlyBytes)
|
||||
ErrorOr<NonnullOwnPtr<GzipCompressor>> GzipCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS | 16, compression_level));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) GzipCompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<GzipCompressor>> GzipCompressor::create(MaybeOwned<Stream> output_stream)
|
||||
ErrorOr<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
BlockHeader header;
|
||||
header.identification_1 = 0x1f;
|
||||
header.identification_2 = 0x8b;
|
||||
header.compression_method = 0x08;
|
||||
header.flags = 0;
|
||||
header.modification_time = 0;
|
||||
header.extra_flags = 3; // DEFLATE sets 2 for maximum compression and 4 for minimum compression
|
||||
header.operating_system = 3; // unix
|
||||
TRY(output_stream->write_until_depleted({ &header, sizeof(header) }));
|
||||
|
||||
auto deflate_compressor = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream)));
|
||||
return adopt_own(*new GzipCompressor { move(output_stream), move(deflate_compressor) });
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(MaybeOwned<Stream> output_stream, NonnullOwnPtr<DeflateCompressor> deflate_compressor)
|
||||
: m_output_stream(move(output_stream))
|
||||
, m_deflate_compressor(move(deflate_compressor))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GzipCompressor::read_some(Bytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> GzipCompressor::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
|
||||
TRY(m_deflate_compressor->write_until_depleted(bytes));
|
||||
m_total_bytes += bytes.size();
|
||||
m_crc32.update(bytes);
|
||||
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
ErrorOr<void> GzipCompressor::finish()
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
m_finished = true;
|
||||
|
||||
TRY(m_deflate_compressor->final_flush());
|
||||
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_crc32.digest()));
|
||||
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_total_bytes));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GzipCompressor::is_eof() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GzipCompressor::is_open() const
|
||||
{
|
||||
return m_output_stream->is_open();
|
||||
}
|
||||
|
||||
void GzipCompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
|
||||
auto gzip_stream = TRY(GzipCompressor::create(MaybeOwned { *output_stream }));
|
||||
|
||||
TRY(gzip_stream->write_until_depleted(bytes));
|
||||
TRY(gzip_stream->finish());
|
||||
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
|
||||
TRY(output_stream->read_until_filled(buffer.bytes()));
|
||||
|
||||
return buffer;
|
||||
return ::Compress::compress_all<GzipCompressor>(bytes, compression_level);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,109 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
||||
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <LibCompress/Deflate.h>
|
||||
#include <LibCrypto/Checksum/CRC32.h>
|
||||
#include <LibCompress/Zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
constexpr u8 gzip_magic_1 = 0x1f;
|
||||
constexpr u8 gzip_magic_2 = 0x8b;
|
||||
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 GzipDecompressor final : public Stream {
|
||||
class GzipDecompressor final : public GenericZlibDecompressor {
|
||||
public:
|
||||
GzipDecompressor(MaybeOwned<Stream>);
|
||||
~GzipDecompressor();
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override { return true; }
|
||||
virtual void close() override { }
|
||||
bool is_likely_compressed(ReadonlyBytes bytes);
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<GzipDecompressor>> create(MaybeOwned<Stream>);
|
||||
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
|
||||
|
||||
static ErrorOr<Optional<String>> describe_header(ReadonlyBytes);
|
||||
static bool is_likely_compressed(ReadonlyBytes bytes);
|
||||
|
||||
private:
|
||||
class Member {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<Member>> construct(BlockHeader header, LittleEndianInputBitStream&);
|
||||
|
||||
BlockHeader m_header;
|
||||
NonnullOwnPtr<DeflateDecompressor> m_stream;
|
||||
Crypto::Checksum::CRC32 m_checksum;
|
||||
size_t m_nread { 0 };
|
||||
|
||||
private:
|
||||
Member(BlockHeader, NonnullOwnPtr<DeflateDecompressor>);
|
||||
};
|
||||
|
||||
Member const& current_member() const { return *m_current_member; }
|
||||
Member& current_member() { return *m_current_member; }
|
||||
|
||||
NonnullOwnPtr<LittleEndianInputBitStream> m_input_stream;
|
||||
u8 m_partial_header[sizeof(BlockHeader)];
|
||||
size_t m_partial_header_offset { 0 };
|
||||
OwnPtr<Member> m_current_member {};
|
||||
|
||||
bool m_eof { false };
|
||||
GzipDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class GzipCompressor final : public Stream {
|
||||
class GzipCompressor final : public GenericZlibCompressor {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<GzipCompressor>> create(MaybeOwned<Stream>);
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes);
|
||||
|
||||
ErrorOr<void> finish();
|
||||
static ErrorOr<NonnullOwnPtr<GzipCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
|
||||
private:
|
||||
GzipCompressor(MaybeOwned<Stream>, NonnullOwnPtr<DeflateCompressor>);
|
||||
|
||||
MaybeOwned<Stream> m_output_stream;
|
||||
NonnullOwnPtr<DeflateCompressor> m_deflate_compressor;
|
||||
|
||||
Crypto::Checksum::CRC32 m_crc32;
|
||||
size_t m_total_bytes { 0 };
|
||||
bool m_finished { false };
|
||||
GzipCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibCompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,175 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/IntegralMath.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCompress/Deflate.h>
|
||||
#include <LibCompress/Zlib.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<ZlibDecompressor>> ZlibDecompressor::create(MaybeOwned<Stream> stream)
|
||||
{
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(stream)));
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibDecompressor::new_z_stream(MAX_WBITS));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibDecompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ZlibDecompressor::ZlibDecompressor(MaybeOwned<Stream> stream)
|
||||
: m_has_seen_header(false)
|
||||
, m_stream(move(stream))
|
||||
ErrorOr<ByteBuffer> ZlibDecompressor::decompress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
return ::Compress::decompress_all<ZlibDecompressor>(bytes);
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> ZlibDecompressor::read_some(Bytes bytes)
|
||||
ErrorOr<NonnullOwnPtr<ZlibCompressor>> ZlibCompressor::create(MaybeOwned<Stream> stream, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
if (!m_has_seen_header) {
|
||||
auto header = TRY(m_stream->read_value<ZlibHeader>());
|
||||
|
||||
if (header.compression_method != ZlibCompressionMethod::Deflate || header.compression_info > 7)
|
||||
return Error::from_string_literal("Non-DEFLATE compression inside Zlib is not supported");
|
||||
|
||||
if (header.present_dictionary)
|
||||
return Error::from_string_literal("Zlib compression with a pre-defined dictionary is currently not supported");
|
||||
|
||||
if (header.as_u16 % 31 != 0)
|
||||
return Error::from_string_literal("Zlib error correction code does not match");
|
||||
|
||||
auto bit_stream = make<LittleEndianInputBitStream>(move(m_stream));
|
||||
auto deflate_stream = TRY(Compress::DeflateDecompressor::construct(move(bit_stream)));
|
||||
|
||||
m_stream = move(deflate_stream);
|
||||
m_has_seen_header = true;
|
||||
}
|
||||
return m_stream->read_some(bytes);
|
||||
auto buffer = TRY(AK::FixedArray<u8>::create(16 * 1024));
|
||||
auto zstream = TRY(GenericZlibCompressor::new_z_stream(MAX_WBITS, compression_level));
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(buffer), move(stream), zstream));
|
||||
}
|
||||
|
||||
ErrorOr<size_t> ZlibDecompressor::write_some(ReadonlyBytes)
|
||||
ErrorOr<ByteBuffer> ZlibCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
bool ZlibDecompressor::is_eof() const
|
||||
{
|
||||
return m_stream->is_eof();
|
||||
}
|
||||
|
||||
bool ZlibDecompressor::is_open() const
|
||||
{
|
||||
return m_stream->is_open();
|
||||
}
|
||||
|
||||
void ZlibDecompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<ZlibCompressor>> ZlibCompressor::construct(MaybeOwned<Stream> stream, ZlibCompressionLevel compression_level)
|
||||
{
|
||||
// Zlib only defines Deflate as a compression method.
|
||||
auto compression_method = ZlibCompressionMethod::Deflate;
|
||||
|
||||
// FIXME: Find a way to compress with Deflate's "Best" compression level.
|
||||
auto compressor_stream = TRY(DeflateCompressor::construct(MaybeOwned(*stream), static_cast<DeflateCompressor::CompressionLevel>(compression_level)));
|
||||
|
||||
auto zlib_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) ZlibCompressor(move(stream), move(compressor_stream))));
|
||||
TRY(zlib_compressor->write_header(compression_method, compression_level));
|
||||
|
||||
return zlib_compressor;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(MaybeOwned<Stream> stream, NonnullOwnPtr<Stream> compressor_stream)
|
||||
: m_output_stream(move(stream))
|
||||
, m_compressor(move(compressor_stream))
|
||||
{
|
||||
}
|
||||
|
||||
ZlibCompressor::~ZlibCompressor() = default;
|
||||
|
||||
ErrorOr<void> ZlibCompressor::write_header(ZlibCompressionMethod compression_method, ZlibCompressionLevel compression_level)
|
||||
{
|
||||
u8 compression_info = 0;
|
||||
if (compression_method == ZlibCompressionMethod::Deflate) {
|
||||
compression_info = AK::log2(DeflateCompressor::window_size) - 8;
|
||||
VERIFY(compression_info <= 7);
|
||||
}
|
||||
|
||||
ZlibHeader header {
|
||||
.compression_method = compression_method,
|
||||
.compression_info = compression_info,
|
||||
.check_bits = 0,
|
||||
.present_dictionary = false,
|
||||
.compression_level = compression_level,
|
||||
};
|
||||
header.check_bits = 0b11111 - header.as_u16 % 31;
|
||||
|
||||
// FIXME: Support pre-defined dictionaries.
|
||||
|
||||
TRY(m_output_stream->write_value(header.as_u16));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> ZlibCompressor::read_some(Bytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> ZlibCompressor::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
|
||||
size_t n_written = TRY(m_compressor->write_some(bytes));
|
||||
m_adler32_checksum.update(bytes.trim(n_written));
|
||||
return n_written;
|
||||
}
|
||||
|
||||
bool ZlibCompressor::is_eof() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ZlibCompressor::is_open() const
|
||||
{
|
||||
return m_output_stream->is_open();
|
||||
}
|
||||
|
||||
void ZlibCompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> ZlibCompressor::finish()
|
||||
{
|
||||
VERIFY(!m_finished);
|
||||
|
||||
if (is<DeflateCompressor>(m_compressor.ptr()))
|
||||
TRY(static_cast<DeflateCompressor*>(m_compressor.ptr())->final_flush());
|
||||
|
||||
NetworkOrdered<u32> adler_sum = m_adler32_checksum.digest();
|
||||
TRY(m_output_stream->write_value(adler_sum));
|
||||
|
||||
m_finished = true;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> ZlibCompressor::compress_all(ReadonlyBytes bytes, ZlibCompressionLevel compression_level)
|
||||
{
|
||||
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
|
||||
auto zlib_stream = TRY(ZlibCompressor::construct(MaybeOwned<Stream>(*output_stream), compression_level));
|
||||
|
||||
TRY(zlib_stream->write_until_depleted(bytes));
|
||||
|
||||
TRY(zlib_stream->finish());
|
||||
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
|
||||
TRY(output_stream->read_until_filled(buffer.bytes()));
|
||||
|
||||
return buffer;
|
||||
return ::Compress::compress_all<ZlibCompressor>(bytes, compression_level);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -7,87 +8,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/Adler32.h>
|
||||
#include <LibCompress/GenericZlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
enum class ZlibCompressionMethod : u8 {
|
||||
Deflate = 8,
|
||||
};
|
||||
|
||||
enum class ZlibCompressionLevel : u8 {
|
||||
Fastest,
|
||||
Fast,
|
||||
Default,
|
||||
Best,
|
||||
};
|
||||
|
||||
struct ZlibHeader {
|
||||
union {
|
||||
struct {
|
||||
ZlibCompressionMethod compression_method : 4;
|
||||
u8 compression_info : 4;
|
||||
|
||||
u8 check_bits : 5;
|
||||
bool present_dictionary : 1;
|
||||
ZlibCompressionLevel compression_level : 2;
|
||||
};
|
||||
NetworkOrdered<u16> as_u16;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ZlibHeader) == sizeof(u16));
|
||||
|
||||
class ZlibDecompressor : public Stream {
|
||||
class ZlibDecompressor final : public GenericZlibDecompressor {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<ZlibDecompressor>> create(MaybeOwned<Stream>);
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
|
||||
|
||||
private:
|
||||
ZlibDecompressor(MaybeOwned<Stream>);
|
||||
|
||||
bool m_has_seen_header { false };
|
||||
MaybeOwned<Stream> m_stream;
|
||||
ZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibDecompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ZlibCompressor : public Stream {
|
||||
class ZlibCompressor final : public GenericZlibCompressor {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<ZlibCompressor>> construct(MaybeOwned<Stream>, ZlibCompressionLevel = ZlibCompressionLevel::Default);
|
||||
~ZlibCompressor();
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
ErrorOr<void> finish();
|
||||
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, ZlibCompressionLevel = ZlibCompressionLevel::Default);
|
||||
static ErrorOr<NonnullOwnPtr<ZlibCompressor>> create(MaybeOwned<Stream>, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default);
|
||||
|
||||
private:
|
||||
ZlibCompressor(MaybeOwned<Stream> stream, NonnullOwnPtr<Stream> compressor_stream);
|
||||
ErrorOr<void> write_header(ZlibCompressionMethod, ZlibCompressionLevel);
|
||||
|
||||
bool m_finished { false };
|
||||
MaybeOwned<Stream> m_output_stream;
|
||||
NonnullOwnPtr<Stream> m_compressor;
|
||||
Crypto::Checksum::Adler32 m_adler32_checksum;
|
||||
ZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: GenericZlibCompressor(move(buffer), move(stream), zstream)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Compress::ZlibHeader> : public AK::DefaultTraits<Compress::ZlibHeader> {
|
||||
static constexpr bool is_trivially_serializable() { return true; }
|
||||
};
|
||||
|
|
|
@ -13,10 +13,13 @@ set(SOURCES
|
|||
|
||||
if (WIN32)
|
||||
list(APPEND SOURCES
|
||||
ProcessWindows.cpp
|
||||
SocketpairWindows.cpp
|
||||
SystemWindows.cpp)
|
||||
else()
|
||||
list(APPEND SOURCES System.cpp)
|
||||
list(APPEND SOURCES
|
||||
Process.cpp
|
||||
System.cpp)
|
||||
endif()
|
||||
|
||||
serenity_lib(LibCoreMinimal coreminimal)
|
||||
|
@ -52,14 +55,12 @@ set(SOURCES
|
|||
if (WIN32)
|
||||
# FIXME: Support UDPServer and TCPServer on Windows
|
||||
list(APPEND SOURCES
|
||||
ProcessWindows.cpp
|
||||
SocketWindows.cpp
|
||||
AnonymousBufferWindows.cpp
|
||||
EventLoopImplementationWindows.cpp)
|
||||
else()
|
||||
list(APPEND SOURCES
|
||||
Command.cpp
|
||||
Process.cpp
|
||||
Socket.cpp
|
||||
AnonymousBuffer.cpp
|
||||
EventLoopImplementationUnix.cpp
|
||||
|
@ -122,12 +123,20 @@ if (ENABLE_SWIFT)
|
|||
else()
|
||||
list(APPEND SWIFT_EXCLUDE_HEADERS "EventLoopImplementationWindows.h")
|
||||
endif()
|
||||
if (NOT APPLE)
|
||||
list(APPEND SWIFT_EXCLUDE_HEADERS
|
||||
IOSurface.h
|
||||
MachPort.h
|
||||
MachMessageTypes.h
|
||||
ProcessStatisticsMach.h
|
||||
)
|
||||
endif()
|
||||
|
||||
generate_clang_module_map(LibCore EXCLUDE_FILES ${SWIFT_EXCLUDE_HEADERS})
|
||||
target_sources(LibCore PRIVATE
|
||||
EventSwift.mm
|
||||
EventLoopExecutor.swift)
|
||||
set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
set_source_files_properties(EventSwift.mm PRIVATE PROPERTIES COMPILE_FLAGS -fblocks)
|
||||
target_link_libraries(LibCore PRIVATE AK)
|
||||
add_swift_target_properties(LibCore LAGOM_LIBRARIES AK)
|
||||
endif()
|
||||
|
|
|
@ -7,16 +7,10 @@
|
|||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/EventSwift.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
# error "This file requires ARC"
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
void deferred_invoke_block(EventLoop& event_loop, void (^invokee)(void))
|
||||
{
|
||||
event_loop.deferred_invoke([invokee = move(invokee)] {
|
||||
invokee();
|
||||
});
|
||||
event_loop.deferred_invoke(invokee);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Core {
|
||||
|
|
|
@ -193,15 +193,7 @@ ErrorOr<void> PosixSocketHelper::set_blocking(bool enabled)
|
|||
|
||||
ErrorOr<void> PosixSocketHelper::set_close_on_exec(bool enabled)
|
||||
{
|
||||
int flags = TRY(System::fcntl(m_fd, F_GETFD));
|
||||
|
||||
if (enabled)
|
||||
flags |= FD_CLOEXEC;
|
||||
else
|
||||
flags &= ~FD_CLOEXEC;
|
||||
|
||||
TRY(System::fcntl(m_fd, F_SETFD, flags));
|
||||
return {};
|
||||
return System::set_close_on_exec(m_fd, enabled);
|
||||
}
|
||||
|
||||
ErrorOr<void> PosixSocketHelper::set_receive_timeout(AK::Duration timeout)
|
||||
|
|
|
@ -106,9 +106,7 @@ ErrorOr<void> PosixSocketHelper::set_blocking(bool)
|
|||
|
||||
ErrorOr<void> PosixSocketHelper::set_close_on_exec(bool enabled)
|
||||
{
|
||||
if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT))
|
||||
return Error::from_windows_error();
|
||||
return {};
|
||||
return System::set_close_on_exec(m_fd, enabled);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> PosixSocketHelper::pending_bytes() const
|
||||
|
|
|
@ -157,8 +157,7 @@ ByteString StandardPaths::config_directory()
|
|||
ByteString StandardPaths::user_data_directory()
|
||||
{
|
||||
#ifdef AK_OS_WINDOWS
|
||||
dbgln("Core::StandardPaths::user_data_directory() is not implemented");
|
||||
VERIFY_NOT_REACHED();
|
||||
return ByteString::formatted("{}/Ladybird"sv, getenv("LOCALAPPDATA"));
|
||||
#endif
|
||||
if (auto data_directory = get_environment_if_not_empty("XDG_DATA_HOME"sv); data_directory.has_value())
|
||||
return LexicalPath::canonicalized_path(*data_directory);
|
||||
|
|
|
@ -1017,4 +1017,17 @@ ErrorOr<void> sleep_ms(u32 milliseconds)
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> set_close_on_exec(int fd, bool enabled)
|
||||
{
|
||||
int flags = TRY(fcntl(fd, F_GETFD));
|
||||
|
||||
if (enabled)
|
||||
flags |= FD_CLOEXEC;
|
||||
else
|
||||
flags &= ~FD_CLOEXEC;
|
||||
|
||||
TRY(fcntl(fd, F_SETFD, flags));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -184,5 +184,6 @@ ErrorOr<void> set_resource_limits(int resource, rlim_t limit);
|
|||
int getpid();
|
||||
bool is_socket(int fd);
|
||||
ErrorOr<void> sleep_ms(u32 milliseconds);
|
||||
ErrorOr<void> set_close_on_exec(int fd, bool enabled);
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <LibCore/Process.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <direct.h>
|
||||
#include <sys/mman.h>
|
||||
|
@ -263,4 +264,16 @@ u64 physical_memory_bytes()
|
|||
return ms.ullTotalPhys;
|
||||
}
|
||||
|
||||
ErrorOr<ByteString> current_executable_path()
|
||||
{
|
||||
return TRY(Process::get_name()).to_byte_string();
|
||||
}
|
||||
|
||||
ErrorOr<void> set_close_on_exec(int handle, bool enabled)
|
||||
{
|
||||
if (!SetHandleInformation(to_handle(handle), HANDLE_FLAG_INHERIT, enabled ? 0 : HANDLE_FLAG_INHERIT))
|
||||
return Error::from_windows_error();
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation(
|
|||
/**
|
||||
* Complexity: O(N) where N is the number of words
|
||||
*/
|
||||
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
|
||||
FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
|
||||
UnsignedBigInteger const& right,
|
||||
size_t index,
|
||||
UnsignedBigInteger& output)
|
||||
|
@ -139,16 +139,16 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
|
|||
// If the value is invalid, the output value is invalid as well.
|
||||
if (right.is_invalid()) {
|
||||
output.invalidate();
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
output.set_to_0();
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
size_t size = (index + UnsignedBigInteger::BITS_IN_WORD - 1) / UnsignedBigInteger::BITS_IN_WORD;
|
||||
|
||||
output.m_words.resize_and_keep_capacity(size);
|
||||
TRY(output.m_words.try_resize_and_keep_capacity(size));
|
||||
VERIFY(size > 0);
|
||||
for (size_t i = 0; i < size - 1; ++i)
|
||||
output.m_words[i] = ~(i < right.length() ? right.words()[i] : 0);
|
||||
|
@ -158,6 +158,8 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
|
|||
auto last_word = last_word_index < right.length() ? right.words()[last_word_index] : 0;
|
||||
|
||||
output.m_words[last_word_index] = (NumericLimits<UnsignedBigInteger::Word>::max() >> (UnsignedBigInteger::BITS_IN_WORD - index)) & ~last_word;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
FLATTEN void UnsignedBigIntegerAlgorithms::shift_left_without_allocation(
|
||||
|
|
|
@ -20,13 +20,13 @@ public:
|
|||
static void bitwise_or_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_and_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_xor_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
|
||||
static void shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
|
||||
static void shift_right_without_allocation(UnsignedBigInteger const& number, size_t num_bits, UnsignedBigInteger& output);
|
||||
static void multiply_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& output);
|
||||
static void divide_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger const& denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
|
||||
static void divide_u16_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger::Word denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
|
||||
|
||||
static ErrorOr<void> bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
|
||||
static ErrorOr<void> try_shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
|
||||
|
||||
static void extended_GCD_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& x, UnsignedBigInteger& y, UnsignedBigInteger& gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t);
|
||||
|
|
|
@ -291,6 +291,17 @@ FLATTEN SignedBigInteger SignedBigInteger::shift_right(size_t num_bits) const
|
|||
return SignedBigInteger { m_unsigned_data.shift_right(num_bits), m_sign };
|
||||
}
|
||||
|
||||
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::mod_power_of_two(size_t power_of_two) const
|
||||
{
|
||||
auto const lower_bits = m_unsigned_data.as_n_bits(power_of_two);
|
||||
|
||||
if (is_positive())
|
||||
return SignedBigInteger(lower_bits);
|
||||
|
||||
// twos encode lower bits
|
||||
return SignedBigInteger(TRY(lower_bits.try_bitwise_not_fill_to_one_based_index(power_of_two)).plus(1).as_n_bits(power_of_two));
|
||||
}
|
||||
|
||||
FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(SignedBigInteger const& other) const
|
||||
{
|
||||
bool result_sign = m_sign ^ other.m_sign;
|
||||
|
|
|
@ -120,6 +120,7 @@ public:
|
|||
[[nodiscard]] SignedBigInteger multiplied_by(SignedBigInteger const& other) const;
|
||||
[[nodiscard]] SignedDivisionResult divided_by(SignedBigInteger const& divisor) const;
|
||||
|
||||
[[nodiscard]] ErrorOr<SignedBigInteger> mod_power_of_two(size_t power_of_two) const;
|
||||
[[nodiscard]] ErrorOr<SignedBigInteger> try_shift_left(size_t num_bits) const;
|
||||
|
||||
[[nodiscard]] SignedBigInteger plus(UnsignedBigInteger const& other) const;
|
||||
|
|
|
@ -473,10 +473,15 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(UnsignedBigInteger co
|
|||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t size) const
|
||||
{
|
||||
return MUST(try_bitwise_not_fill_to_one_based_index(size));
|
||||
}
|
||||
|
||||
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::try_bitwise_not_fill_to_one_based_index(size_t size) const
|
||||
{
|
||||
UnsignedBigInteger result;
|
||||
|
||||
UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result);
|
||||
TRY(UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -506,6 +511,33 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_right(size_t num_bits) cons
|
|||
return output;
|
||||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::as_n_bits(size_t n) const
|
||||
{
|
||||
if (auto const num_bits = one_based_index_of_highest_set_bit(); n >= num_bits)
|
||||
return *this;
|
||||
|
||||
UnsignedBigInteger output;
|
||||
output.set_to(*this);
|
||||
|
||||
auto const word_index = n / BITS_IN_WORD;
|
||||
|
||||
auto const bits_to_keep = n % BITS_IN_WORD;
|
||||
auto const bits_to_discard = BITS_IN_WORD - bits_to_keep;
|
||||
|
||||
output.m_words.resize(word_index + 1);
|
||||
|
||||
auto const last_word = output.m_words[word_index];
|
||||
Word new_last_word = 0;
|
||||
|
||||
// avoid UB from a 32 bit shift on a u32
|
||||
if (bits_to_keep != 0)
|
||||
new_last_word = last_word << bits_to_discard >> bits_to_discard;
|
||||
|
||||
output.m_words[word_index] = new_last_word;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const
|
||||
{
|
||||
UnsignedBigInteger result;
|
||||
|
|
|
@ -117,9 +117,11 @@ public:
|
|||
[[nodiscard]] UnsignedBigInteger bitwise_not_fill_to_one_based_index(size_t) const;
|
||||
[[nodiscard]] UnsignedBigInteger shift_left(size_t num_bits) const;
|
||||
[[nodiscard]] UnsignedBigInteger shift_right(size_t num_bits) const;
|
||||
[[nodiscard]] UnsignedBigInteger as_n_bits(size_t n) const;
|
||||
[[nodiscard]] UnsignedBigInteger multiplied_by(UnsignedBigInteger const& other) const;
|
||||
[[nodiscard]] UnsignedDivisionResult divided_by(UnsignedBigInteger const& divisor) const;
|
||||
|
||||
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_bitwise_not_fill_to_one_based_index(size_t) const;
|
||||
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_shift_left(size_t num_bits) const;
|
||||
|
||||
[[nodiscard]] u32 hash() const;
|
||||
|
|
|
@ -17,9 +17,6 @@ set(SOURCES
|
|||
BigInt/SignedBigInteger.cpp
|
||||
BigInt/UnsignedBigInteger.cpp
|
||||
Certificate/Certificate.cpp
|
||||
Checksum/Adler32.cpp
|
||||
Checksum/cksum.cpp
|
||||
Checksum/CRC32.cpp
|
||||
Cipher/AES.cpp
|
||||
Curves/EdwardsCurve.cpp
|
||||
Curves/SECPxxxr1.cpp
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/Adler32.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
void Adler32::update(ReadonlyBytes data)
|
||||
{
|
||||
// See https://github.com/SerenityOS/serenity/pull/24408#discussion_r1609051678
|
||||
constexpr size_t iterations_without_overflow = 380368439;
|
||||
|
||||
u64 state_a = m_state_a;
|
||||
u64 state_b = m_state_b;
|
||||
while (data.size()) {
|
||||
// You can verify that no overflow will happen here during at least
|
||||
// `iterations_without_overflow` iterations using the following Python script:
|
||||
//
|
||||
// state_a = 65520
|
||||
// state_b = 65520
|
||||
// for i in range(380368439):
|
||||
// state_a += 255
|
||||
// state_b += state_a
|
||||
// print(state_b < 2 ** 64)
|
||||
auto chunk = data.slice(0, min(data.size(), iterations_without_overflow));
|
||||
for (u8 byte : chunk) {
|
||||
state_a += byte;
|
||||
state_b += state_a;
|
||||
}
|
||||
state_a %= 65521;
|
||||
state_b %= 65521;
|
||||
data = data.slice(chunk.size());
|
||||
}
|
||||
m_state_a = state_a;
|
||||
m_state_b = state_b;
|
||||
}
|
||||
|
||||
u32 Adler32::digest()
|
||||
{
|
||||
return (m_state_b << 16) | m_state_a;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#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() = default;
|
||||
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);
|
||||
}
|
||||
|
||||
virtual void update(ReadonlyBytes data) override;
|
||||
virtual u32 digest() override;
|
||||
|
||||
private:
|
||||
u32 m_state_a { 1 };
|
||||
u32 m_state_b { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/ChecksumFunction.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
// A generic 16-bit Cyclic Redundancy Check.
|
||||
// Just like CRC32, this class receives its polynomial little-endian.
|
||||
// For example, the polynomial x¹⁶ + x¹² + x⁵ + 1 is represented as 0x8408.
|
||||
template<u16 polynomial>
|
||||
class CRC16 : public ChecksumFunction<u16> {
|
||||
public:
|
||||
static constexpr u16 be_polynomial = bitswap(polynomial);
|
||||
|
||||
// This is a big endian table, while CRC-32 uses a little endian table.
|
||||
static constexpr auto generate_table()
|
||||
{
|
||||
Array<u16, 256> data {};
|
||||
data[0] = 0;
|
||||
u16 value = 0x8000;
|
||||
auto i = 1u;
|
||||
do {
|
||||
if ((value & 0x8000) != 0) {
|
||||
value = be_polynomial ^ (value << 1);
|
||||
} else {
|
||||
value = value << 1;
|
||||
}
|
||||
|
||||
for (auto j = 0u; j < i; ++j) {
|
||||
data[i + j] = value ^ data[j];
|
||||
}
|
||||
i <<= 1;
|
||||
} while (i < 256);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static constexpr auto table = generate_table();
|
||||
|
||||
virtual ~CRC16() = default;
|
||||
|
||||
CRC16() = default;
|
||||
CRC16(ReadonlyBytes data)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
CRC16(u16 initial_state, ReadonlyBytes data)
|
||||
: m_state(initial_state)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
// FIXME: This implementation is naive and slow.
|
||||
// Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 16-bit polynomials.
|
||||
virtual void update(ReadonlyBytes data) override
|
||||
{
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
size_t table_index = ((m_state >> 8) ^ data.at(i)) & 0xFF;
|
||||
m_state = (table[table_index] ^ (static_cast<u32>(m_state) << 8)) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
virtual u16 digest() override
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
private:
|
||||
u16 m_state { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/CRC32.h>
|
||||
|
||||
#ifdef __ARM_ACLE
|
||||
# include <arm_acle.h>
|
||||
#endif
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
#if __ARM_ARCH >= 8 && defined(__ARM_FEATURE_CRC32) && defined(__ARM_ACLE)
|
||||
void CRC32::update(ReadonlyBytes span)
|
||||
{
|
||||
// FIXME: Does this require runtime checking on rpi?
|
||||
// (Maybe the instruction is present on the rpi4 but not on the rpi3?)
|
||||
|
||||
u8 const* data = span.data();
|
||||
size_t size = span.size();
|
||||
|
||||
while (size > 0 && (reinterpret_cast<FlatPtr>(data) & 7) != 0) {
|
||||
m_state = __crc32b(m_state, *data);
|
||||
++data;
|
||||
--size;
|
||||
}
|
||||
|
||||
auto* data64 = reinterpret_cast<u64 const*>(data);
|
||||
while (size >= 8) {
|
||||
m_state = __crc32d(m_state, *data64);
|
||||
++data64;
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
data = reinterpret_cast<u8 const*>(data64);
|
||||
while (size > 0) {
|
||||
m_state = __crc32b(m_state, *data);
|
||||
++data;
|
||||
--size;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Intel, use _mm_crc32_u8 / _mm_crc32_u64 if available (SSE 4.2).
|
||||
|
||||
#else
|
||||
|
||||
static constexpr size_t ethernet_polynomial = 0xEDB88320;
|
||||
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
|
||||
// This implements Intel's slicing-by-8 algorithm. Their original paper is no longer on their website,
|
||||
// but their source code is still available for reference:
|
||||
// https://sourceforge.net/projects/slicing-by-8/
|
||||
static constexpr auto generate_table()
|
||||
{
|
||||
Array<Array<u32, 256>, 8> data {};
|
||||
|
||||
for (u32 i = 0; i < 256; ++i) {
|
||||
auto value = i;
|
||||
|
||||
for (size_t j = 0; j < 8; ++j)
|
||||
value = (value >> 1) ^ ((value & 1) * ethernet_polynomial);
|
||||
|
||||
data[0][i] = value;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 256; ++i) {
|
||||
for (size_t j = 1; j < 8; ++j)
|
||||
data[j][i] = (data[j - 1][i] >> 8) ^ data[0][data[j - 1][i] & 0xff];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static constexpr auto table = generate_table();
|
||||
|
||||
struct AlignmentData {
|
||||
ReadonlyBytes misaligned;
|
||||
ReadonlyBytes aligned;
|
||||
};
|
||||
|
||||
static AlignmentData split_bytes_for_alignment(ReadonlyBytes data, size_t alignment)
|
||||
{
|
||||
auto address = reinterpret_cast<uintptr_t>(data.data());
|
||||
auto offset = alignment - address % alignment;
|
||||
|
||||
if (offset == alignment)
|
||||
return { {}, data };
|
||||
|
||||
if (data.size() < alignment)
|
||||
return { data, {} };
|
||||
|
||||
return { data.trim(offset), data.slice(offset) };
|
||||
}
|
||||
|
||||
static constexpr u32 single_byte_crc(u32 crc, u8 byte)
|
||||
{
|
||||
return (crc >> 8) ^ table[0][(crc & 0xff) ^ byte];
|
||||
}
|
||||
|
||||
void CRC32::update(ReadonlyBytes data)
|
||||
{
|
||||
// The provided data may not be aligned to a 4-byte boundary, required to reinterpret its address
|
||||
// into a u32 in the loop below. So we split the bytes into two segments: the misaligned bytes
|
||||
// (which undergo the standard 1-byte-at-a-time algorithm) and remaining aligned bytes.
|
||||
auto [misaligned_data, aligned_data] = split_bytes_for_alignment(data, alignof(u32));
|
||||
|
||||
for (auto byte : misaligned_data)
|
||||
m_state = single_byte_crc(m_state, byte);
|
||||
|
||||
while (aligned_data.size() >= 8) {
|
||||
auto const* segment = reinterpret_cast<u32 const*>(aligned_data.data());
|
||||
auto low = *segment ^ m_state;
|
||||
auto high = *(++segment);
|
||||
|
||||
m_state = table[0][(high >> 24) & 0xff]
|
||||
^ table[1][(high >> 16) & 0xff]
|
||||
^ table[2][(high >> 8) & 0xff]
|
||||
^ table[3][high & 0xff]
|
||||
^ table[4][(low >> 24) & 0xff]
|
||||
^ table[5][(low >> 16) & 0xff]
|
||||
^ table[6][(low >> 8) & 0xff]
|
||||
^ table[7][low & 0xff];
|
||||
|
||||
aligned_data = aligned_data.slice(8);
|
||||
}
|
||||
|
||||
for (auto byte : aligned_data)
|
||||
m_state = single_byte_crc(m_state, byte);
|
||||
}
|
||||
|
||||
# else
|
||||
|
||||
// FIXME: Implement the slicing-by-8 algorithm for big endian CPUs.
|
||||
static constexpr auto generate_table()
|
||||
{
|
||||
Array<u32, 256> data {};
|
||||
for (auto i = 0u; i < data.size(); i++) {
|
||||
u32 value = i;
|
||||
|
||||
for (auto j = 0; j < 8; j++) {
|
||||
if (value & 1) {
|
||||
value = ethernet_polynomial ^ (value >> 1);
|
||||
} else {
|
||||
value = value >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
data[i] = value;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static constexpr auto table = generate_table();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
# endif
|
||||
#endif
|
||||
|
||||
u32 CRC32::digest()
|
||||
{
|
||||
return ~m_state;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/ChecksumFunction.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
class CRC32 : public ChecksumFunction<u32> {
|
||||
public:
|
||||
CRC32() = default;
|
||||
CRC32(ReadonlyBytes data)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
CRC32(u32 initial_state, ReadonlyBytes data)
|
||||
: m_state(initial_state)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
virtual void update(ReadonlyBytes data) override;
|
||||
virtual u32 digest() override;
|
||||
|
||||
private:
|
||||
u32 m_state { ~0u };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/ChecksumFunction.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
// A generic 8-bit Cyclic Redundancy Check.
|
||||
// Note that as opposed to CRC32, this class operates with MSB first, so the polynomial must not be reversed.
|
||||
// For example, the polynomial x⁸ + x² + x + 1 is represented as 0x07 and not 0xE0.
|
||||
template<u8 polynomial>
|
||||
class CRC8 : public ChecksumFunction<u8> {
|
||||
public:
|
||||
// This is a big endian table, while CRC-32 uses a little endian table.
|
||||
static constexpr auto generate_table()
|
||||
{
|
||||
Array<u8, 256> data {};
|
||||
u8 value = 0x80;
|
||||
auto i = 1u;
|
||||
do {
|
||||
if ((value & 0x80) != 0) {
|
||||
value = polynomial ^ (value << 1);
|
||||
} else {
|
||||
value = value << 1;
|
||||
}
|
||||
|
||||
for (auto j = 0u; j < i; ++j) {
|
||||
data[i + j] = value ^ data[j];
|
||||
}
|
||||
i <<= 1;
|
||||
} while (i < 256);
|
||||
return data;
|
||||
}
|
||||
|
||||
static constexpr auto table = generate_table();
|
||||
|
||||
virtual ~CRC8() = default;
|
||||
|
||||
CRC8() = default;
|
||||
CRC8(ReadonlyBytes data)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
CRC8(u8 initial_state, ReadonlyBytes data)
|
||||
: m_state(initial_state)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
// FIXME: This implementation is naive and slow.
|
||||
// Figure out how to adopt the slicing-by-8 algorithm (see CRC32) for 8 bit polynomials.
|
||||
virtual void update(ReadonlyBytes data) override
|
||||
{
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
size_t table_index = (m_state ^ data.at(i)) & 0xFF;
|
||||
m_state = (table[table_index] ^ (static_cast<u32>(m_state) << 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
virtual u8 digest() override
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 m_state { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Span.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
template<typename ChecksumT>
|
||||
class ChecksumFunction {
|
||||
public:
|
||||
using ChecksumType = ChecksumT;
|
||||
|
||||
virtual void update(ReadonlyBytes data) = 0;
|
||||
virtual ChecksumType digest() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~ChecksumFunction() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <LibCrypto/Checksum/ChecksumFunction.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
// A stream wrapper type which passes all read and written data through a checksum function.
|
||||
template<typename ChecksumFunctionType, typename ChecksumType = typename ChecksumFunctionType::ChecksumType>
|
||||
requires(
|
||||
IsBaseOf<ChecksumFunction<ChecksumType>, ChecksumFunctionType>,
|
||||
// Require checksum function to be constructible without arguments, since we have no initial data.
|
||||
requires() {
|
||||
ChecksumFunctionType {};
|
||||
})
|
||||
class ChecksummingStream : public Stream {
|
||||
public:
|
||||
virtual ~ChecksummingStream() = default;
|
||||
|
||||
ChecksummingStream(MaybeOwned<Stream> stream)
|
||||
: m_stream(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes bytes) override
|
||||
{
|
||||
auto const written_bytes = TRY(m_stream->read_some(bytes));
|
||||
update(written_bytes);
|
||||
return written_bytes;
|
||||
}
|
||||
|
||||
virtual ErrorOr<void> read_until_filled(Bytes bytes) override
|
||||
{
|
||||
TRY(m_stream->read_until_filled(bytes));
|
||||
update(bytes);
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
|
||||
{
|
||||
auto bytes_written = TRY(m_stream->write_some(bytes));
|
||||
// Only update with the bytes that were actually written
|
||||
update(bytes.trim(bytes_written));
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
virtual ErrorOr<void> write_until_depleted(ReadonlyBytes bytes) override
|
||||
{
|
||||
update(bytes);
|
||||
return m_stream->write_until_depleted(bytes);
|
||||
}
|
||||
|
||||
virtual bool is_eof() const override { return m_stream->is_eof(); }
|
||||
virtual bool is_open() const override { return m_stream->is_open(); }
|
||||
virtual void close() override { m_stream->close(); }
|
||||
|
||||
ChecksumType digest()
|
||||
{
|
||||
return m_checksum.digest();
|
||||
}
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE void update(ReadonlyBytes bytes)
|
||||
{
|
||||
m_checksum.update(bytes);
|
||||
}
|
||||
|
||||
MaybeOwned<Stream> m_stream;
|
||||
ChecksumFunctionType m_checksum {};
|
||||
};
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCrypto/Checksum/cksum.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
static constexpr u32 table[256] = {
|
||||
0x00000000,
|
||||
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
|
||||
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
|
||||
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
|
||||
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
|
||||
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
|
||||
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
|
||||
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
|
||||
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
|
||||
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
|
||||
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
|
||||
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
|
||||
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
|
||||
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
|
||||
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
|
||||
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
|
||||
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
|
||||
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
|
||||
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
|
||||
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
|
||||
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
|
||||
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
|
||||
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
|
||||
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
|
||||
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
|
||||
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
|
||||
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
|
||||
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
|
||||
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
|
||||
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
|
||||
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
|
||||
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
|
||||
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
|
||||
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
|
||||
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
|
||||
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
|
||||
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
|
||||
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
|
||||
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
|
||||
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
|
||||
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
void cksum::update(ReadonlyBytes data)
|
||||
{
|
||||
for (u8 byte : data)
|
||||
m_state = (m_state << 8) ^ table[((m_state >> 24) ^ byte) & 0xFF];
|
||||
|
||||
m_size += data.size();
|
||||
}
|
||||
|
||||
u32 cksum::digest()
|
||||
{
|
||||
u32 size = m_size;
|
||||
u32 state = m_state;
|
||||
|
||||
while (size) {
|
||||
state = (state << 8) ^ table[((state >> 24) ^ size) & 0xFF];
|
||||
size >>= 8;
|
||||
}
|
||||
|
||||
state = ~state & 0xFFFFFFFF;
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCrypto/Checksum/ChecksumFunction.h>
|
||||
|
||||
namespace Crypto::Checksum {
|
||||
|
||||
class cksum : public ChecksumFunction<u32> {
|
||||
public:
|
||||
cksum() = default;
|
||||
cksum(ReadonlyBytes data)
|
||||
{
|
||||
update(data);
|
||||
}
|
||||
|
||||
virtual void update(ReadonlyBytes data) override;
|
||||
virtual u32 digest() override;
|
||||
|
||||
private:
|
||||
u32 m_state { 0 };
|
||||
off_t m_size { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -66,6 +66,7 @@ void FrameActor::handle_message(Message const& message)
|
|||
|
||||
if (message.type == "detach"sv) {
|
||||
if (auto tab = m_tab.strong_ref()) {
|
||||
devtools().delegate().stop_listening_for_dom_properties(tab->description());
|
||||
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
|
||||
devtools().delegate().stop_listening_for_console_messages(tab->description());
|
||||
devtools().delegate().stop_listening_for_style_sheet_sources(tab->description());
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <LibDevTools/Actors/InspectorActor.h>
|
||||
#include <LibDevTools/Actors/PageStyleActor.h>
|
||||
|
@ -14,47 +16,47 @@
|
|||
|
||||
namespace DevTools {
|
||||
|
||||
static void received_layout(JsonObject& response, JsonObject const& computed_style, JsonObject const& node_box_sizing)
|
||||
static void received_layout(JsonObject& response, JsonObject const& node_box_sizing)
|
||||
{
|
||||
response.set("autoMargins"sv, JsonObject {});
|
||||
|
||||
auto pixel_value = [&](auto const& object, auto key) {
|
||||
return object.get_double_with_precision_loss(key).value_or(0);
|
||||
auto pixel_value = [&](auto key) {
|
||||
return node_box_sizing.get_double_with_precision_loss(key).value_or(0);
|
||||
};
|
||||
auto set_pixel_value_from = [&](auto const& object, auto object_key, auto message_key) {
|
||||
response.set(message_key, MUST(String::formatted("{}px", pixel_value(object, object_key))));
|
||||
auto set_pixel_value = [&](auto key) {
|
||||
response.set(key, MUST(String::formatted("{}px", pixel_value(key))));
|
||||
};
|
||||
auto set_computed_value_from = [&](auto const& object, auto key) {
|
||||
response.set(key, object.get_string(key).value_or(String {}));
|
||||
auto set_computed_value = [&](auto key) {
|
||||
response.set(key, node_box_sizing.get_string(key).value_or(String {}));
|
||||
};
|
||||
|
||||
response.set("width"sv, pixel_value(node_box_sizing, "content_width"sv));
|
||||
response.set("height"sv, pixel_value(node_box_sizing, "content_height"sv));
|
||||
|
||||
// FIXME: This response should also contain "top", "right", "bottom", and "left", but our box model metrics in
|
||||
// WebContent do not provide this information.
|
||||
|
||||
set_pixel_value_from(node_box_sizing, "border_top"sv, "border-top-width"sv);
|
||||
set_pixel_value_from(node_box_sizing, "border_right"sv, "border-right-width"sv);
|
||||
set_pixel_value_from(node_box_sizing, "border_bottom"sv, "border-bottom-width"sv);
|
||||
set_pixel_value_from(node_box_sizing, "border_left"sv, "border-left-width"sv);
|
||||
set_computed_value("width"sv);
|
||||
set_computed_value("height"sv);
|
||||
|
||||
set_pixel_value_from(node_box_sizing, "margin_top"sv, "margin-top"sv);
|
||||
set_pixel_value_from(node_box_sizing, "margin_right"sv, "margin-right"sv);
|
||||
set_pixel_value_from(node_box_sizing, "margin_bottom"sv, "margin-bottom"sv);
|
||||
set_pixel_value_from(node_box_sizing, "margin_left"sv, "margin-left"sv);
|
||||
set_pixel_value("border-top-width"sv);
|
||||
set_pixel_value("border-right-width"sv);
|
||||
set_pixel_value("border-bottom-width"sv);
|
||||
set_pixel_value("border-left-width"sv);
|
||||
|
||||
set_pixel_value_from(node_box_sizing, "padding_top"sv, "padding-top"sv);
|
||||
set_pixel_value_from(node_box_sizing, "padding_right"sv, "padding-right"sv);
|
||||
set_pixel_value_from(node_box_sizing, "padding_bottom"sv, "padding-bottom"sv);
|
||||
set_pixel_value_from(node_box_sizing, "padding_left"sv, "padding-left"sv);
|
||||
set_pixel_value("margin-top"sv);
|
||||
set_pixel_value("margin-right"sv);
|
||||
set_pixel_value("margin-bottom"sv);
|
||||
set_pixel_value("margin-left"sv);
|
||||
|
||||
set_computed_value_from(computed_style, "box-sizing"sv);
|
||||
set_computed_value_from(computed_style, "display"sv);
|
||||
set_computed_value_from(computed_style, "float"sv);
|
||||
set_computed_value_from(computed_style, "line-height"sv);
|
||||
set_computed_value_from(computed_style, "position"sv);
|
||||
set_computed_value_from(computed_style, "z-index"sv);
|
||||
set_pixel_value("padding-top"sv);
|
||||
set_pixel_value("padding-right"sv);
|
||||
set_pixel_value("padding-bottom"sv);
|
||||
set_pixel_value("padding-left"sv);
|
||||
|
||||
set_computed_value("box-sizing"sv);
|
||||
set_computed_value("display"sv);
|
||||
set_computed_value("float"sv);
|
||||
set_computed_value("line-height"sv);
|
||||
set_computed_value("position"sv);
|
||||
set_computed_value("z-index"sv);
|
||||
}
|
||||
|
||||
static void received_computed_style(JsonObject& response, JsonObject const& computed_style)
|
||||
|
@ -111,9 +113,20 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr<In
|
|||
: Actor(devtools, move(name))
|
||||
, m_inspector(move(inspector))
|
||||
{
|
||||
if (auto tab = InspectorActor::tab_for(m_inspector)) {
|
||||
devtools.delegate().listen_for_dom_properties(tab->description(),
|
||||
[weak_self = make_weak_ptr<PageStyleActor>()](WebView::DOMNodeProperties const& properties) {
|
||||
if (auto self = weak_self.strong_ref())
|
||||
self->received_dom_node_properties(properties);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PageStyleActor::~PageStyleActor() = default;
|
||||
PageStyleActor::~PageStyleActor()
|
||||
{
|
||||
if (auto tab = InspectorActor::tab_for(m_inspector))
|
||||
devtools().delegate().stop_listening_for_dom_properties(tab->description());
|
||||
}
|
||||
|
||||
void PageStyleActor::handle_message(Message const& message)
|
||||
{
|
||||
|
@ -134,38 +147,17 @@ void PageStyleActor::handle_message(Message const& message)
|
|||
}
|
||||
|
||||
if (message.type == "getComputed"sv) {
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
return;
|
||||
|
||||
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
|
||||
received_computed_style(response, properties.computed_style);
|
||||
});
|
||||
|
||||
inspect_dom_node(message, WebView::DOMNodeProperties::Type::ComputedStyle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getLayout"sv) {
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
return;
|
||||
|
||||
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
|
||||
received_layout(response, properties.computed_style, properties.node_box_sizing);
|
||||
});
|
||||
|
||||
inspect_dom_node(message, WebView::DOMNodeProperties::Type::Layout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getUsedFontFaces"sv) {
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
return;
|
||||
|
||||
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
|
||||
received_fonts(response, properties.fonts);
|
||||
});
|
||||
|
||||
inspect_dom_node(message, WebView::DOMNodeProperties::Type::UsedFonts);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -192,19 +184,46 @@ JsonValue PageStyleActor::serialize_style() const
|
|||
return style;
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void PageStyleActor::inspect_dom_node(Message const& message, StringView node_actor, Callback&& callback)
|
||||
void PageStyleActor::inspect_dom_node(Message const& message, WebView::DOMNodeProperties::Type property_type)
|
||||
{
|
||||
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), node_actor);
|
||||
auto node = get_required_parameter<String>(message, "node"sv);
|
||||
if (!node.has_value())
|
||||
return;
|
||||
|
||||
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), *node);
|
||||
if (!dom_node.has_value()) {
|
||||
send_unknown_actor_error(message, node_actor);
|
||||
send_unknown_actor_error(message, *node);
|
||||
return;
|
||||
}
|
||||
|
||||
devtools().delegate().inspect_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element,
|
||||
async_handler(message, [callback = forward<Callback>(callback)](auto&, auto properties, auto& response) {
|
||||
callback(response, properties);
|
||||
}));
|
||||
devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element);
|
||||
m_pending_inspect_requests.append({ .id = message.id });
|
||||
}
|
||||
|
||||
void PageStyleActor::received_dom_node_properties(WebView::DOMNodeProperties const& properties)
|
||||
{
|
||||
if (m_pending_inspect_requests.is_empty())
|
||||
return;
|
||||
|
||||
JsonObject response;
|
||||
|
||||
switch (properties.type) {
|
||||
case WebView::DOMNodeProperties::Type::ComputedStyle:
|
||||
if (properties.properties.is_object())
|
||||
received_computed_style(response, properties.properties.as_object());
|
||||
break;
|
||||
case WebView::DOMNodeProperties::Type::Layout:
|
||||
if (properties.properties.is_object())
|
||||
received_layout(response, properties.properties.as_object());
|
||||
break;
|
||||
case WebView::DOMNodeProperties::Type::UsedFonts:
|
||||
if (properties.properties.is_array())
|
||||
received_fonts(response, properties.properties.as_array());
|
||||
break;
|
||||
}
|
||||
|
||||
auto message = m_pending_inspect_requests.take_first();
|
||||
send_response(message, move(response));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,19 +6,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibDevTools/Actor.h>
|
||||
#include <LibWebView/DOMNodeProperties.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
struct DOMNodeProperties {
|
||||
JsonObject computed_style;
|
||||
JsonObject node_box_sizing;
|
||||
JsonArray fonts;
|
||||
};
|
||||
|
||||
class PageStyleActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "page-style"sv;
|
||||
|
@ -33,10 +26,12 @@ private:
|
|||
|
||||
virtual void handle_message(Message const&) override;
|
||||
|
||||
template<typename Callback>
|
||||
void inspect_dom_node(Message const&, StringView node_actor, Callback&&);
|
||||
void inspect_dom_node(Message const&, WebView::DOMNodeProperties::Type);
|
||||
void received_dom_node_properties(WebView::DOMNodeProperties const&);
|
||||
|
||||
WeakPtr<InspectorActor> m_inspector;
|
||||
|
||||
Vector<Message, 1> m_pending_inspect_requests;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWebView/DOMNodeProperties.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
@ -31,8 +32,10 @@ public:
|
|||
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
|
||||
|
||||
using OnDOMNodeInspectionComplete = Function<void(ErrorOr<DOMNodeProperties>)>;
|
||||
virtual void inspect_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>, OnDOMNodeInspectionComplete) const { }
|
||||
using OnDOMNodePropertiesReceived = Function<void(WebView::DOMNodeProperties)>;
|
||||
virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { }
|
||||
virtual void stop_listening_for_dom_properties(TabDescription const&) const { }
|
||||
virtual void inspect_dom_node(TabDescription const&, WebView::DOMNodeProperties::Type, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
|
||||
virtual void clear_inspected_dom_node(TabDescription const&) const { }
|
||||
|
||||
virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
|
||||
|
|
|
@ -468,29 +468,31 @@ size_t GIFImageDecoderPlugin::first_animated_frame_index()
|
|||
|
||||
ErrorOr<ImageFrameDescriptor> GIFImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
|
||||
{
|
||||
if (m_context->error_state >= GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
|
||||
if (m_context->error_state == GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame) {
|
||||
return Error::from_string_literal("GIFImageDecoderPlugin: Decoding failed");
|
||||
}
|
||||
|
||||
if (m_context->state < GIFLoadingContext::State::FrameDescriptorsLoaded) {
|
||||
if (auto result = load_gif_frame_descriptors(*m_context); result.is_error()) {
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToLoadFrameDescriptors;
|
||||
return result.release_error();
|
||||
|
||||
// If we failed to load frame descriptors but we have some images, we can still try to decode them.
|
||||
if (m_context->images.is_empty()) {
|
||||
return result.release_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_context->error_state == GIFLoadingContext::ErrorState::NoError) {
|
||||
if (auto result = decode_frame(*m_context, index); result.is_error()) {
|
||||
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
|
||||
return result.release_error();
|
||||
}
|
||||
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
|
||||
return result.release_error();
|
||||
}
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
|
||||
if (auto result = decode_frame(*m_context, index); result.is_error()) {
|
||||
if (m_context->state < GIFLoadingContext::State::FrameComplete) {
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
|
||||
return result.release_error();
|
||||
}
|
||||
if (auto result = decode_frame(*m_context, 0); result.is_error()) {
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAnyFrame;
|
||||
return result.release_error();
|
||||
}
|
||||
m_context->error_state = GIFLoadingContext::ErrorState::FailedToDecodeAllFrames;
|
||||
}
|
||||
|
||||
ImageFrameDescriptor frame {};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/QuickSort.h>
|
||||
|
|
|
@ -11,25 +11,11 @@
|
|||
|
||||
namespace IPC {
|
||||
|
||||
// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding.
|
||||
// Perhaps we should add an option to IPC::File to allow the receiver to decide whether to
|
||||
// make it O_CLOEXEC or not. Or an attribute in the .ipc file?
|
||||
ErrorOr<void> File::clear_close_on_exec()
|
||||
{
|
||||
auto fd_flags = TRY(Core::System::fcntl(m_fd, F_GETFD));
|
||||
fd_flags &= ~FD_CLOEXEC;
|
||||
TRY(Core::System::fcntl(m_fd, F_SETFD, fd_flags));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<File> decode(Decoder& decoder)
|
||||
{
|
||||
auto file = TRY(decoder.files().try_dequeue());
|
||||
auto fd = file.fd();
|
||||
|
||||
auto fd_flags = TRY(Core::System::fcntl(fd, F_GETFD));
|
||||
TRY(Core::System::fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC));
|
||||
TRY(Core::System::set_close_on_exec(file.fd(), true));
|
||||
return file;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,13 @@ public:
|
|||
return exchange(m_fd, -1);
|
||||
}
|
||||
|
||||
ErrorOr<void> clear_close_on_exec();
|
||||
// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding.
|
||||
// Perhaps we should add an option to IPC::File to allow the receiver to decide whether to
|
||||
// make it O_CLOEXEC or not. Or an attribute in the .ipc file?
|
||||
ErrorOr<void> clear_close_on_exec()
|
||||
{
|
||||
return Core::System::set_close_on_exec(m_fd, false);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit File(int fd)
|
||||
|
|
|
@ -12,13 +12,6 @@
|
|||
|
||||
namespace IPC {
|
||||
|
||||
ErrorOr<void> File::clear_close_on_exec()
|
||||
{
|
||||
if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
|
||||
return Error::from_windows_error();
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<File> decode(Decoder& decoder)
|
||||
{
|
||||
|
|
|
@ -1616,9 +1616,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> VariableDeclaration::ge
|
|||
} else if (m_declaration_kind != DeclarationKind::Var) {
|
||||
(void)TRY(assign_value_to_variable_declarator(generator, declarator, *this, generator.add_constant(js_undefined())));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& declarator : m_declarations) {
|
||||
if (auto const* identifier = declarator->target().get_pointer<NonnullRefPtr<Identifier const>>()) {
|
||||
if ((*identifier)->is_local()) {
|
||||
generator.set_local_initialized((*identifier)->local_variable_index());
|
||||
|
|
|
@ -31,7 +31,9 @@ struct PropertyLookupCache {
|
|||
|
||||
struct GlobalVariableCache : public PropertyLookupCache {
|
||||
u64 environment_serial_number { 0 };
|
||||
Optional<u32> environment_binding_index;
|
||||
u32 environment_binding_index { 0 };
|
||||
bool has_environment_binding_index { false };
|
||||
bool in_module_environment { false };
|
||||
};
|
||||
|
||||
struct SourceRecord {
|
||||
|
|
|
@ -33,7 +33,15 @@ Generator::Generator(VM& vm, GC::Ptr<ECMAScriptFunctionObject const> function, M
|
|||
CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function)
|
||||
{
|
||||
if (function.m_has_parameter_expressions) {
|
||||
emit<Op::CreateLexicalEnvironment>();
|
||||
bool has_non_local_parameters = false;
|
||||
for (auto const& parameter_name : function.m_parameter_names) {
|
||||
if (parameter_name.value == ECMAScriptFunctionObject::ParameterIsLocal::No) {
|
||||
has_non_local_parameters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_non_local_parameters)
|
||||
emit<Op::CreateLexicalEnvironment>();
|
||||
}
|
||||
|
||||
for (auto const& parameter_name : function.m_parameter_names) {
|
||||
|
@ -128,7 +136,19 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
}
|
||||
}
|
||||
} else {
|
||||
emit<Op::CreateVariableEnvironment>(function.m_var_environment_bindings_count);
|
||||
bool has_non_local_parameters = false;
|
||||
if (scope_body) {
|
||||
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
|
||||
auto const& id = variable_to_initialize.identifier;
|
||||
if (!id.is_local()) {
|
||||
has_non_local_parameters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_non_local_parameters)
|
||||
emit<Op::CreateVariableEnvironment>(function.m_var_environment_bindings_count);
|
||||
|
||||
if (scope_body) {
|
||||
for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) {
|
||||
|
@ -164,8 +184,8 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
}
|
||||
|
||||
if (!function.m_strict) {
|
||||
bool can_elide_declarative_environment = !function.m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations());
|
||||
if (!can_elide_declarative_environment) {
|
||||
bool can_elide_lexical_environment = !scope_body || !scope_body->has_non_local_lexical_declarations();
|
||||
if (!can_elide_lexical_environment) {
|
||||
emit<Op::CreateLexicalEnvironment>(function.m_lex_environment_bindings_count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -26,6 +26,7 @@
|
|||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/MathObject.h>
|
||||
#include <LibJS/Runtime/ModuleEnvironment.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/ObjectEnvironment.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
|
@ -36,6 +37,30 @@
|
|||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <LibJS/SourceTextModule.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct PropertyKeyAndEnumerableFlag {
|
||||
JS::PropertyKey key;
|
||||
bool enumerable { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
template<>
|
||||
struct Traits<JS::PropertyKeyAndEnumerableFlag> : public DefaultTraits<JS::PropertyKeyAndEnumerableFlag> {
|
||||
static unsigned hash(JS::PropertyKeyAndEnumerableFlag const& entry)
|
||||
{
|
||||
return Traits<JS::PropertyKey>::hash(entry.key);
|
||||
}
|
||||
|
||||
static bool equals(JS::PropertyKeyAndEnumerableFlag const& a, JS::PropertyKeyAndEnumerableFlag const& b)
|
||||
{
|
||||
return Traits<JS::PropertyKey>::equals(a.key, b.key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
bool g_dump_bytecode = false;
|
||||
|
@ -1124,20 +1149,31 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
|
|||
|
||||
// OPTIMIZATION: For global lexical bindings, if the global declarative environment hasn't changed,
|
||||
// we can use the cached environment binding index.
|
||||
if (cache.environment_binding_index.has_value())
|
||||
return declarative_record.get_binding_value_direct(vm, cache.environment_binding_index.value());
|
||||
if (cache.has_environment_binding_index) {
|
||||
if (cache.in_module_environment) {
|
||||
auto module = vm.running_execution_context().script_or_module.get_pointer<GC::Ref<Module>>();
|
||||
return (*module)->environment()->get_binding_value_direct(vm, cache.environment_binding_index);
|
||||
}
|
||||
return declarative_record.get_binding_value_direct(vm, cache.environment_binding_index);
|
||||
}
|
||||
}
|
||||
|
||||
cache.environment_serial_number = declarative_record.environment_serial_number();
|
||||
|
||||
auto& identifier = interpreter.current_executable().get_identifier(identifier_index);
|
||||
|
||||
if (vm.running_execution_context().script_or_module.has<GC::Ref<Module>>()) {
|
||||
if (auto* module = vm.running_execution_context().script_or_module.get_pointer<GC::Ref<Module>>()) {
|
||||
// NOTE: GetGlobal is used to access variables stored in the module environment and global environment.
|
||||
// The module environment is checked first since it precedes the global environment in the environment chain.
|
||||
auto& module_environment = *vm.running_execution_context().script_or_module.get<GC::Ref<Module>>()->environment();
|
||||
if (TRY(module_environment.has_binding(identifier))) {
|
||||
// TODO: Cache offset of binding value
|
||||
auto& module_environment = *(*module)->environment();
|
||||
Optional<size_t> index;
|
||||
if (TRY(module_environment.has_binding(identifier, &index))) {
|
||||
if (index.has_value()) {
|
||||
cache.environment_binding_index = static_cast<u32>(index.value());
|
||||
cache.has_environment_binding_index = true;
|
||||
cache.in_module_environment = true;
|
||||
return TRY(module_environment.get_binding_value_direct(vm, index.value()));
|
||||
}
|
||||
return TRY(module_environment.get_binding_value(vm, identifier, vm.in_strict_mode()));
|
||||
}
|
||||
}
|
||||
|
@ -1145,6 +1181,8 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
|
|||
Optional<size_t> offset;
|
||||
if (TRY(declarative_record.has_binding(identifier, &offset))) {
|
||||
cache.environment_binding_index = static_cast<u32>(offset.value());
|
||||
cache.has_environment_binding_index = true;
|
||||
cache.in_module_environment = false;
|
||||
return TRY(declarative_record.get_binding_value(vm, identifier, vm.in_strict_mode()));
|
||||
}
|
||||
|
||||
|
@ -1699,8 +1737,8 @@ inline ThrowCompletionOr<Object*> get_object_property_iterator(VM& vm, Value val
|
|||
auto object = TRY(value.to_object(vm));
|
||||
// Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys,
|
||||
// so we just keep the order consistent anyway.
|
||||
OrderedHashTable<PropertyKey> properties;
|
||||
OrderedHashTable<PropertyKey> non_enumerable_properties;
|
||||
|
||||
OrderedHashTable<PropertyKeyAndEnumerableFlag> properties;
|
||||
HashTable<GC::Ref<Object>> seen_objects;
|
||||
// Collect all keys immediately (invariant no. 5)
|
||||
for (auto object_to_check = GC::Ptr { object.ptr() }; object_to_check && !seen_objects.contains(*object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) {
|
||||
|
@ -1708,50 +1746,57 @@ inline ThrowCompletionOr<Object*> get_object_property_iterator(VM& vm, Value val
|
|||
for (auto& key : TRY(object_to_check->internal_own_property_keys())) {
|
||||
if (key.is_symbol())
|
||||
continue;
|
||||
auto property_key = TRY(PropertyKey::from_value(vm, key));
|
||||
|
||||
// If there is a non-enumerable property higher up the prototype chain with the same key,
|
||||
// we mustn't include this property even if it's enumerable (invariant no. 5 and 6)
|
||||
if (non_enumerable_properties.contains(property_key))
|
||||
continue;
|
||||
if (properties.contains(property_key))
|
||||
// NOTE: If there is a non-enumerable property higher up the prototype chain with the same key,
|
||||
// we mustn't include this property even if it's enumerable (invariant no. 5 and 6)
|
||||
// This is achieved with the PropertyKeyAndEnumerableFlag struct, which doesn't consider
|
||||
// the enumerable flag when comparing keys.
|
||||
PropertyKeyAndEnumerableFlag new_entry {
|
||||
.key = TRY(PropertyKey::from_value(vm, key)),
|
||||
.enumerable = false,
|
||||
};
|
||||
|
||||
if (properties.contains(new_entry))
|
||||
continue;
|
||||
|
||||
auto descriptor = TRY(object_to_check->internal_get_own_property(property_key));
|
||||
if (!*descriptor->enumerable)
|
||||
non_enumerable_properties.set(move(property_key));
|
||||
else
|
||||
properties.set(move(property_key));
|
||||
auto descriptor = TRY(object_to_check->internal_get_own_property(new_entry.key));
|
||||
if (!descriptor.has_value())
|
||||
continue;
|
||||
|
||||
new_entry.enumerable = *descriptor->enumerable;
|
||||
properties.set(move(new_entry), AK::HashSetExistingEntryBehavior::Keep);
|
||||
}
|
||||
}
|
||||
auto& realm = *vm.current_realm();
|
||||
auto callback = NativeFunction::create(
|
||||
*vm.current_realm(), [items = move(properties)](VM& vm) mutable -> ThrowCompletionOr<Value> {
|
||||
auto& realm = *vm.current_realm();
|
||||
auto iterated_object_value = vm.this_value();
|
||||
if (!iterated_object_value.is_object())
|
||||
return vm.throw_completion<InternalError>("Invalid state for GetObjectPropertyIterator.next"sv);
|
||||
|
||||
auto& iterated_object = iterated_object_value.as_object();
|
||||
auto result_object = Object::create(realm, nullptr);
|
||||
properties.remove_all_matching([&](auto& entry) { return !entry.enumerable; });
|
||||
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
auto result_object = Object::create_with_premade_shape(realm.intrinsics().iterator_result_object_shape());
|
||||
auto value_offset = realm.intrinsics().iterator_result_object_value_offset();
|
||||
auto done_offset = realm.intrinsics().iterator_result_object_done_offset();
|
||||
|
||||
auto callback = NativeFunction::create(
|
||||
*vm.current_realm(), [items = move(properties), result_object, value_offset, done_offset](VM& vm) mutable -> ThrowCompletionOr<Value> {
|
||||
auto& iterated_object = vm.this_value().as_object();
|
||||
while (true) {
|
||||
if (items.is_empty()) {
|
||||
result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
|
||||
result_object->put_direct(done_offset, JS::Value(true));
|
||||
return result_object;
|
||||
}
|
||||
|
||||
auto key = items.take_first();
|
||||
auto key = move(items.take_first().key);
|
||||
|
||||
// If the property is deleted, don't include it (invariant no. 2)
|
||||
if (!TRY(iterated_object.has_property(key)))
|
||||
continue;
|
||||
|
||||
result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes);
|
||||
result_object->put_direct(done_offset, JS::Value(false));
|
||||
|
||||
if (key.is_number())
|
||||
result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, String::number(key.as_number())), default_attributes);
|
||||
result_object->put_direct(value_offset, PrimitiveString::create(vm, String::number(key.as_number())));
|
||||
else if (key.is_string())
|
||||
result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, key.as_string()), default_attributes);
|
||||
result_object->put_direct(value_offset, PrimitiveString::create(vm, key.as_string()));
|
||||
else
|
||||
VERIFY_NOT_REACHED(); // We should not have non-string/number keys.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -189,6 +189,7 @@ class Intrinsics;
|
|||
class IteratorRecord;
|
||||
class MemberExpression;
|
||||
class MetaProperty;
|
||||
class ModuleEnvironment;
|
||||
class Module;
|
||||
struct ModuleRequest;
|
||||
class NativeFunction;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
|
||||
*
|
||||
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <LibJS/CyclicModule.h>
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Runtime/ModuleEnvironment.h>
|
||||
#include <LibJS/Runtime/ModuleNamespaceObject.h>
|
||||
#include <LibJS/Runtime/ModuleRequest.h>
|
||||
#include <LibJS/Runtime/Promise.h>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -100,7 +100,7 @@ public:
|
|||
|
||||
StringView filename() const { return m_filename; }
|
||||
|
||||
Environment* environment() { return m_environment; }
|
||||
GC::Ptr<ModuleEnvironment> environment() { return m_environment; }
|
||||
|
||||
Script::HostDefined* host_defined() const { return m_host_defined; }
|
||||
|
||||
|
@ -122,7 +122,7 @@ protected:
|
|||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
void set_environment(Environment* environment)
|
||||
void set_environment(GC::Ref<ModuleEnvironment> environment)
|
||||
{
|
||||
m_environment = environment;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ private:
|
|||
// stores modules with a RefPtr we cannot just store the VM as that leads to
|
||||
// cycles.
|
||||
GC::Ptr<Realm> m_realm; // [[Realm]]
|
||||
GC::Ptr<Environment> m_environment; // [[Environment]]
|
||||
GC::Ptr<ModuleEnvironment> m_environment; // [[Environment]]
|
||||
GC::Ptr<Object> m_namespace; // [[Namespace]]
|
||||
Script::HostDefined* m_host_defined { nullptr }; // [[HostDefined]]
|
||||
|
||||
|
|
|
@ -743,19 +743,19 @@ ErrorOr<void> print_intl_plural_rules(JS::PrintContext& print_context, JS::Intl:
|
|||
ErrorOr<void> print_intl_collator(JS::PrintContext& print_context, JS::Intl::Collator const& collator, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Intl.Collator"sv));
|
||||
out("\n locale: ");
|
||||
TRY(js_out(print_context, "\n locale: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.locale()), seen_objects));
|
||||
out("\n usage: ");
|
||||
TRY(js_out(print_context, "\n usage: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.usage_string()), seen_objects));
|
||||
out("\n sensitivity: ");
|
||||
TRY(js_out(print_context, "\n sensitivity: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.sensitivity_string()), seen_objects));
|
||||
out("\n caseFirst: ");
|
||||
TRY(js_out(print_context, "\n caseFirst: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.case_first_string()), seen_objects));
|
||||
out("\n collation: ");
|
||||
TRY(js_out(print_context, "\n collation: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(collator.vm(), collator.collation()), seen_objects));
|
||||
out("\n ignorePunctuation: ");
|
||||
TRY(js_out(print_context, "\n ignorePunctuation: "));
|
||||
TRY(print_value(print_context, JS::Value(collator.ignore_punctuation()), seen_objects));
|
||||
out("\n numeric: ");
|
||||
TRY(js_out(print_context, "\n numeric: "));
|
||||
TRY(print_value(print_context, JS::Value(collator.numeric()), seen_objects));
|
||||
return {};
|
||||
}
|
||||
|
@ -763,9 +763,9 @@ ErrorOr<void> print_intl_collator(JS::PrintContext& print_context, JS::Intl::Col
|
|||
ErrorOr<void> print_intl_segmenter(JS::PrintContext& print_context, JS::Intl::Segmenter const& segmenter, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Intl.Segmenter"sv));
|
||||
out("\n locale: ");
|
||||
TRY(js_out(print_context, "\n locale: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.locale()), seen_objects));
|
||||
out("\n granularity: ");
|
||||
TRY(js_out(print_context, "\n granularity: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(segmenter.vm(), segmenter.segmenter_granularity_string()), seen_objects));
|
||||
return {};
|
||||
}
|
||||
|
@ -775,62 +775,47 @@ ErrorOr<void> print_intl_segments(JS::PrintContext& print_context, JS::Intl::Seg
|
|||
auto segments_string = JS::Utf16String::create(segments.segments_string());
|
||||
|
||||
TRY(print_type(print_context, "Segments"sv));
|
||||
out("\n string: ");
|
||||
TRY(js_out(print_context, "\n string: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(segments.vm(), move(segments_string)), seen_objects));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_intl_duration_format(JS::PrintContext& print_context, JS::Intl::DurationFormat const& duration_format, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
auto print_style_and_display = [&](StringView style_name, StringView display_name, JS::Intl::DurationFormat::DurationUnitOptions options) -> ErrorOr<void> {
|
||||
auto style = JS::Intl::DurationFormat::value_style_to_string(options.style);
|
||||
auto display = JS::Intl::DurationFormat::display_to_string(options.display);
|
||||
|
||||
TRY(js_out(print_context, "\n {}: ", style_name));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), style), seen_objects));
|
||||
|
||||
TRY(js_out(print_context, "\n {}: ", display_name));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), display), seen_objects));
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
TRY(print_type(print_context, "Intl.DurationFormat"sv));
|
||||
out("\n locale: ");
|
||||
TRY(js_out(print_context, "\n locale: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.locale()), seen_objects));
|
||||
out("\n numberingSystem: ");
|
||||
TRY(js_out(print_context, "\n numberingSystem: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.numbering_system()), seen_objects));
|
||||
out("\n style: ");
|
||||
TRY(js_out(print_context, "\n style: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.style_string()), seen_objects));
|
||||
out("\n years: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_style_string()), seen_objects));
|
||||
out("\n yearsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.years_display_string()), seen_objects));
|
||||
out("\n months: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_style_string()), seen_objects));
|
||||
out("\n monthsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.months_display_string()), seen_objects));
|
||||
out("\n weeks: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_style_string()), seen_objects));
|
||||
out("\n weeksDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.weeks_display_string()), seen_objects));
|
||||
out("\n days: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_style_string()), seen_objects));
|
||||
out("\n daysDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.days_display_string()), seen_objects));
|
||||
out("\n hours: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_style_string()), seen_objects));
|
||||
out("\n hoursDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.hours_display_string()), seen_objects));
|
||||
out("\n minutes: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_style_string()), seen_objects));
|
||||
out("\n minutesDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.minutes_display_string()), seen_objects));
|
||||
out("\n seconds: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_style_string()), seen_objects));
|
||||
out("\n secondsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.seconds_display_string()), seen_objects));
|
||||
out("\n milliseconds: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_style_string()), seen_objects));
|
||||
out("\n millisecondsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.milliseconds_display_string()), seen_objects));
|
||||
out("\n microseconds: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_style_string()), seen_objects));
|
||||
out("\n microsecondsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.microseconds_display_string()), seen_objects));
|
||||
out("\n nanoseconds: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_style_string()), seen_objects));
|
||||
out("\n nanosecondsDisplay: ");
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(duration_format.vm(), duration_format.nanoseconds_display_string()), seen_objects));
|
||||
|
||||
TRY(print_style_and_display("years"sv, "yearsDisplay"sv, duration_format.years_options()));
|
||||
TRY(print_style_and_display("months"sv, "monthsDisplay"sv, duration_format.months_options()));
|
||||
TRY(print_style_and_display("weeks"sv, "weeksDisplay"sv, duration_format.weeks_options()));
|
||||
TRY(print_style_and_display("days"sv, "daysDisplay"sv, duration_format.days_options()));
|
||||
TRY(print_style_and_display("hours"sv, "hoursDisplay"sv, duration_format.hours_options()));
|
||||
TRY(print_style_and_display("minutes"sv, "minutesDisplay"sv, duration_format.minutes_options()));
|
||||
TRY(print_style_and_display("seconds"sv, "secondsDisplay"sv, duration_format.seconds_options()));
|
||||
TRY(print_style_and_display("milliseconds"sv, "millisecondsDisplay"sv, duration_format.milliseconds_options()));
|
||||
TRY(print_style_and_display("microseconds"sv, "microsecondsDisplay"sv, duration_format.microseconds_options()));
|
||||
TRY(print_style_and_display("nanoseconds"sv, "nanosecondsDisplay"sv, duration_format.nanoseconds_options()));
|
||||
|
||||
if (duration_format.has_fractional_digits()) {
|
||||
out("\n fractionalDigits: ");
|
||||
TRY(js_out(print_context, "\n fractionalDigits: "));
|
||||
TRY(print_value(print_context, JS::Value(duration_format.fractional_digits()), seen_objects));
|
||||
}
|
||||
return {};
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace JS {
|
|||
GC_DEFINE_ALLOCATOR(BigIntConstructor);
|
||||
|
||||
static Crypto::SignedBigInteger const BIGINT_ONE { 1 };
|
||||
static Crypto::SignedBigInteger const BIGINT_ZERO { 0 };
|
||||
|
||||
BigIntConstructor::BigIntConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.BigInt.as_string(), realm.intrinsics().function_prototype())
|
||||
|
@ -72,20 +73,25 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n)
|
|||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
auto bigint = TRY(vm.argument(1).to_bigint(vm));
|
||||
|
||||
// OPTIMIZATION: mod = bigint (mod 2^0) = 0 < 2^(0-1) = 0.5
|
||||
if (bits == 0)
|
||||
return BigInt::create(vm, BIGINT_ZERO);
|
||||
|
||||
// 3. Let mod be ℝ(bigint) modulo 2^bits.
|
||||
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
|
||||
// drop the most significant bits.
|
||||
auto bits_shift_left = TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits));
|
||||
auto mod = modulo(bigint->big_integer(), bits_shift_left);
|
||||
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
|
||||
|
||||
// 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); otherwise, return ℤ(mod).
|
||||
// NOTE: Some of the below conditionals are non-standard, but are to protect SignedBigInteger from
|
||||
// allocating an absurd amount of memory if `bits - 1` overflows to NumericLimits<size_t>::max.
|
||||
if ((bits == 0) && (mod >= BIGINT_ONE))
|
||||
return BigInt::create(vm, mod.minus(bits_shift_left));
|
||||
if ((bits > 0) && (mod >= BIGINT_ONE.shift_left(bits - 1)))
|
||||
return BigInt::create(vm, mod.minus(bits_shift_left));
|
||||
// OPTIMIZATION: mod < 2^(bits-1)
|
||||
if (mod.is_zero())
|
||||
return BigInt::create(vm, BIGINT_ZERO);
|
||||
|
||||
// 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); ...
|
||||
if (auto top_bit_index = mod.unsigned_value().one_based_index_of_highest_set_bit(); top_bit_index >= bits) {
|
||||
// twos complement decode
|
||||
auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().try_bitwise_not_fill_to_one_based_index(bits)).plus(1);
|
||||
return BigInt::create(vm, Crypto::SignedBigInteger { std::move(decoded), true });
|
||||
}
|
||||
|
||||
// ... otherwise, return ℤ(mod).
|
||||
return BigInt::create(vm, mod);
|
||||
}
|
||||
|
||||
|
@ -98,10 +104,10 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n)
|
|||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
auto bigint = TRY(vm.argument(1).to_bigint(vm));
|
||||
|
||||
// 3. Return the BigInt value that represents ℝ(bigint) modulo 2bits.
|
||||
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
|
||||
// drop the most significant bits.
|
||||
return BigInt::create(vm, modulo(bigint->big_integer(), TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits))));
|
||||
// 3. Return the BigInt value that represents ℝ(bigint) modulo 2^bits.
|
||||
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
|
||||
|
||||
return BigInt::create(vm, mod);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -439,6 +439,17 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
|
|||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto callee_context = ExecutionContext::create();
|
||||
|
||||
// Non-standard
|
||||
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->passed_argument_count = arguments_list.size();
|
||||
if (arguments_list.size() < m_formal_parameters.size()) {
|
||||
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
|
||||
callee_context->arguments.append(js_undefined());
|
||||
}
|
||||
|
||||
// 1. Let callerContext be the running execution context.
|
||||
// NOTE: No-op, kept by the VM in its execution context stack.
|
||||
|
||||
|
@ -453,17 +464,6 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
|
|||
this_argument = TRY(ordinary_create_from_constructor<Object>(vm, new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag));
|
||||
}
|
||||
|
||||
auto callee_context = ExecutionContext::create();
|
||||
|
||||
// Non-standard
|
||||
callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size()));
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->passed_argument_count = arguments_list.size();
|
||||
if (arguments_list.size() < m_formal_parameters.size()) {
|
||||
for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i)
|
||||
callee_context->arguments.append(js_undefined());
|
||||
}
|
||||
|
||||
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
|
||||
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
|
||||
TRY(prepare_for_ordinary_call(*callee_context, &new_target));
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \
|
||||
M(InvalidFractionDigits, "Fraction Digits must be an integer no less than 0, and no greater than 100") \
|
||||
M(InvalidHint, "Invalid hint: \"{}\"") \
|
||||
M(InvalidIndex, "Index must be a positive integer") \
|
||||
M(InvalidIndex, "Index must be a positive integer no greater than 2^53-1") \
|
||||
M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \
|
||||
M(InvalidLength, "Invalid {} length") \
|
||||
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'") \
|
||||
|
|
|
@ -500,6 +500,8 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
|
|||
// 12. Let supportedKeywords be a new empty List.
|
||||
Vector<Unicode::Keyword> supported_keywords;
|
||||
|
||||
Vector<Unicode::Keyword> icu_keywords;
|
||||
|
||||
// 13. For each element key of relevantExtensionKeys, do
|
||||
for (auto const& key : relevant_extension_keys) {
|
||||
// a. Let keyLocaleData be foundLocaleData.[[<key>]].
|
||||
|
@ -574,10 +576,23 @@ ResolvedLocale resolve_locale(ReadonlySpan<String> requested_locales, LocaleOpti
|
|||
if (supported_keyword.has_value())
|
||||
supported_keywords.append(supported_keyword.release_value());
|
||||
|
||||
if (auto* value_string = value.get_pointer<String>())
|
||||
icu_keywords.empend(MUST(String::from_utf8(key)), *value_string);
|
||||
|
||||
// m. Set result.[[<key>]] to value.
|
||||
find_key_in_value(result, key) = move(value);
|
||||
}
|
||||
|
||||
// AD-HOC: For ICU, we need to form a locale with all relevant extension keys present.
|
||||
if (icu_keywords.is_empty()) {
|
||||
result.icu_locale = found_locale;
|
||||
} else {
|
||||
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);
|
||||
VERIFY(locale_id.has_value());
|
||||
|
||||
result.icu_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), {}, move(icu_keywords));
|
||||
}
|
||||
|
||||
// 14. If supportedKeywords is not empty, then
|
||||
if (!supported_keywords.is_empty()) {
|
||||
auto locale_id = Unicode::parse_unicode_locale_id(found_locale);
|
||||
|
|
|
@ -38,6 +38,7 @@ struct MatchedLocale {
|
|||
|
||||
struct ResolvedLocale {
|
||||
String locale;
|
||||
String icu_locale;
|
||||
LocaleKey ca; // [[Calendar]]
|
||||
LocaleKey co; // [[Collation]]
|
||||
LocaleKey hc; // [[HourCycle]]
|
||||
|
|
|
@ -47,32 +47,32 @@ static Optional<Unicode::DateTimeFormat const&> get_or_create_formatter(StringVi
|
|||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
|
||||
}
|
||||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_year_month_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
|
||||
}
|
||||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_month_day_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
|
||||
}
|
||||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_time_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
|
||||
}
|
||||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_time_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
|
||||
}
|
||||
|
||||
Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_instant_formatter()
|
||||
{
|
||||
return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
|
||||
return get_or_create_formatter(m_icu_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
|
||||
}
|
||||
|
||||
// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern
|
||||
|
|
|
@ -38,6 +38,9 @@ public:
|
|||
String const& locale() const { return m_locale; }
|
||||
void set_locale(String locale) { m_locale = move(locale); }
|
||||
|
||||
String const& icu_locale() const { return m_icu_locale; }
|
||||
void set_icu_locale(String icu_locale) { m_icu_locale = move(icu_locale); }
|
||||
|
||||
String const& calendar() const { return m_calendar; }
|
||||
void set_calendar(String calendar) { m_calendar = move(calendar); }
|
||||
|
||||
|
@ -107,6 +110,7 @@ private:
|
|||
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
|
||||
// Non-standard. Stores the ICU date-time formatters for the Intl object's formatting options.
|
||||
String m_icu_locale;
|
||||
OwnPtr<Unicode::DateTimeFormat> m_formatter;
|
||||
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_date_formatter;
|
||||
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_year_month_formatter;
|
||||
|
|
|
@ -149,6 +149,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
|
|||
|
||||
// 18. Set dateTimeFormat.[[Locale]] to r.[[Locale]].
|
||||
date_time_format->set_locale(move(result.locale));
|
||||
date_time_format->set_icu_locale(move(result.icu_locale));
|
||||
|
||||
// 19. Let resolvedCalendar be r.[[ca]].
|
||||
// 20. Set dateTimeFormat.[[Calendar]] to resolvedCalendar.
|
||||
|
@ -355,7 +356,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
|
|||
// d. Let styles be resolvedLocaleData.[[styles]].[[<resolvedCalendar>]].
|
||||
// e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
|
||||
formatter = Unicode::DateTimeFormat::create_for_date_and_time_style(
|
||||
date_time_format->locale(),
|
||||
date_time_format->icu_locale(),
|
||||
time_zone,
|
||||
format_options.hour_cycle,
|
||||
format_options.hour12,
|
||||
|
@ -443,7 +444,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
|
|||
}
|
||||
|
||||
formatter = Unicode::DateTimeFormat::create_for_pattern_options(
|
||||
date_time_format->locale(),
|
||||
date_time_format->icu_locale(),
|
||||
time_zone,
|
||||
best_format);
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@ static ThrowCompletionOr<void> validate_duration_unit_style(VM& vm, PropertyKey
|
|||
}
|
||||
|
||||
// 13.5.6 GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, prevStyle, twoDigitHours ), https://tc39.es/ecma402/#sec-getdurationunitoptions
|
||||
ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours)
|
||||
ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options(VM& vm, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours)
|
||||
{
|
||||
auto const& unit_property_key = unit_to_property_key(vm, unit);
|
||||
|
||||
|
@ -251,8 +251,8 @@ ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, Duratio
|
|||
style = DurationFormat::value_style_from_string(style_value.as_string().utf8_string_view());
|
||||
}
|
||||
|
||||
// 4. If style is "numeric" and unit is one of "milliseconds", "microseconds", or "nanoseconds", then
|
||||
if (style == DurationFormat::ValueStyle::Numeric && first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)) {
|
||||
// 4. If style is "numeric" and IsFractionalSecondUnitName(unit) is true, then
|
||||
if (style == DurationFormat::ValueStyle::Numeric && is_fractional_second_unit_name(unit)) {
|
||||
// a. Set style to "fractional".
|
||||
style = DurationFormat::ValueStyle::Fractional;
|
||||
|
||||
|
@ -280,8 +280,8 @@ ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM& vm, Duratio
|
|||
style = DurationFormat::ValueStyle::TwoDigit;
|
||||
}
|
||||
|
||||
// 10. Return the Record { [[Style]]: style, [[Display]]: display }.
|
||||
return DurationUnitOptions { .style = style, .display = display };
|
||||
// 10. Return the Duration Unit Options Record { [[Style]]: style, [[Display]]: display }.
|
||||
return DurationFormat::DurationUnitOptions { .style = style, .display = display };
|
||||
}
|
||||
|
||||
// 13.5.7 ComputeFractionalDigits ( durationFormat, duration ), https://tc39.es/ecma402/#sec-computefractionaldigits
|
||||
|
@ -294,25 +294,25 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for
|
|||
// 2. Let exponent be 3.
|
||||
double exponent = 3;
|
||||
|
||||
// 3. For each row of Table 23, except the header row, in table order, do
|
||||
// 3. For each row of Table 24, except the header row, in table order, do
|
||||
for (auto const& duration_instances_component : duration_instances_components) {
|
||||
// a. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row.
|
||||
auto style = (duration_format.*duration_instances_component.get_style_slot)();
|
||||
|
||||
// b. If style is "fractional", then
|
||||
if (style == DurationFormat::ValueStyle::Fractional) {
|
||||
// i. Assert: The Unit value of the current row is "milliseconds", "microseconds", or "nanoseconds".
|
||||
VERIFY(first_is_one_of(duration_instances_component.unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds));
|
||||
// a. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row.
|
||||
auto unit_options = (duration_format.*duration_instances_component.get_internal_slot)();
|
||||
|
||||
// ii. Let value be the value of duration's field whose name is the Value Field value of the current row.
|
||||
// iii. Set value to value / 10**exponent.
|
||||
Crypto::BigFraction value {
|
||||
Crypto::SignedBigInteger { (duration.*duration_instances_component.value_slot)() },
|
||||
Crypto::UnsignedBigInteger { pow(10, exponent) }
|
||||
};
|
||||
// b. If unitOptions.[[Style]] is "fractional", then
|
||||
if (unit_options.style == DurationFormat::ValueStyle::Fractional) {
|
||||
// i. Let unit be the Unit value of the current row.
|
||||
auto unit = duration_instances_component.unit;
|
||||
|
||||
// iv. Set result to result + value.
|
||||
result = result + value;
|
||||
// ii. Assert: IsFractionalSecondUnitName(unit) is true.
|
||||
VERIFY(is_fractional_second_unit_name(unit));
|
||||
|
||||
// iii. Let value be the value of duration's field whose name is the Value Field value of the current row.
|
||||
auto value = (duration.*duration_instances_component.value_slot)();
|
||||
|
||||
// iv. Set result to result + (value / 10**exponent).
|
||||
result = result + Crypto::BigFraction { Crypto::SignedBigInteger { value }, Crypto::UnsignedBigInteger { pow(10, exponent) } };
|
||||
|
||||
// v. Set exponent to exponent + 3.
|
||||
exponent += 3;
|
||||
|
@ -326,22 +326,19 @@ Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_for
|
|||
// 13.5.8 NextUnitFractional ( durationFormat, unit ), https://tc39.es/ecma402/#sec-nextunitfractional
|
||||
bool next_unit_fractional(DurationFormat const& duration_format, DurationFormat::Unit unit)
|
||||
{
|
||||
// 1. Assert: unit is "seconds", "milliseconds", or "microseconds".
|
||||
VERIFY(first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds));
|
||||
|
||||
// 2. If unit is "seconds" and durationFormat.[[MillisecondsStyle]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_style() == DurationFormat::ValueStyle::Fractional)
|
||||
// 1. If unit is "seconds" and durationFormat.[[MillisecondsOptions]].[[Style]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Seconds && duration_format.milliseconds_options().style == DurationFormat::ValueStyle::Fractional)
|
||||
return true;
|
||||
|
||||
// 3. Else if unit is "milliseconds" and durationFormat.[[MicrosecondsStyle]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_style() == DurationFormat::ValueStyle::Fractional)
|
||||
// 2. If unit is "milliseconds" and durationFormat.[[MicrosecondsOptions]].[[Style]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Milliseconds && duration_format.microseconds_options().style == DurationFormat::ValueStyle::Fractional)
|
||||
return true;
|
||||
|
||||
// 4. Else if unit is "microseconds" and durationFormat.[[NanosecondsStyle]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_style() == DurationFormat::ValueStyle::Fractional)
|
||||
// 3. If unit is "microseconds" and durationFormat.[[NanosecondsOptions]].[[Style]] is "fractional", return true.
|
||||
if (unit == DurationFormat::Unit::Microseconds && duration_format.nanoseconds_options().style == DurationFormat::ValueStyle::Fractional)
|
||||
return true;
|
||||
|
||||
// 5. Return false.
|
||||
// 4. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -353,8 +350,8 @@ Vector<DurationFormatPart> format_numeric_hours(VM& vm, DurationFormat const& du
|
|||
// 1. Let result be a new empty List.
|
||||
Vector<DurationFormatPart> result;
|
||||
|
||||
// 2. Let hoursStyle be durationFormat.[[HoursStyle]].
|
||||
auto hours_style = duration_format.hours_style();
|
||||
// 2. Let hoursStyle be durationFormat.[[HoursOptions]].[[Style]].
|
||||
auto hours_style = duration_format.hours_options().style;
|
||||
|
||||
// 3. Assert: hoursStyle is "numeric" or hoursStyle is "2-digit".
|
||||
VERIFY(hours_style == DurationFormat::ValueStyle::Numeric || hours_style == DurationFormat::ValueStyle::TwoDigit);
|
||||
|
@ -418,8 +415,8 @@ Vector<DurationFormatPart> format_numeric_minutes(VM& vm, DurationFormat const&
|
|||
result.append({ .type = "literal"sv, .value = move(separator), .unit = {} });
|
||||
}
|
||||
|
||||
// 3. Let minutesStyle be durationFormat.[[MinutesStyle]].
|
||||
auto minutes_style = duration_format.minutes_style();
|
||||
// 3. Let minutesStyle be durationFormat.[[MinutesOptions]].[[Style]].
|
||||
auto minutes_style = duration_format.minutes_options().style;
|
||||
|
||||
// 4. Assert: minutesStyle is "numeric" or minutesStyle is "2-digit".
|
||||
VERIFY(minutes_style == DurationFormat::ValueStyle::Numeric || minutes_style == DurationFormat::ValueStyle::TwoDigit);
|
||||
|
@ -483,8 +480,8 @@ Vector<DurationFormatPart> format_numeric_seconds(VM& vm, DurationFormat const&
|
|||
result.append({ .type = "literal"sv, .value = move(separator), .unit = {} });
|
||||
}
|
||||
|
||||
// 3. Let secondsStyle be durationFormat.[[SecondsStyle]].
|
||||
auto seconds_style = duration_format.seconds_style();
|
||||
// 3. Let secondsStyle be durationFormat.[[SecondsOptions]].[[Style]].
|
||||
auto seconds_style = duration_format.seconds_options().style;
|
||||
|
||||
// 4. Assert: secondsStyle is "numeric" or secondsStyle is "2-digit".
|
||||
VERIFY(seconds_style == DurationFormat::ValueStyle::Numeric || seconds_style == DurationFormat::ValueStyle::TwoDigit);
|
||||
|
@ -567,14 +564,14 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
|
|||
// 3. Let hoursValue be duration.[[Hours]].
|
||||
auto hours_value = duration.hours();
|
||||
|
||||
// 4. Let hoursDisplay be durationFormat.[[HoursDisplay]].
|
||||
auto hours_display = duration_format.hours_display();
|
||||
// 4. Let hoursDisplay be durationFormat.[[HoursOptions]].[[Display]].
|
||||
auto hours_display = duration_format.hours_options().display;
|
||||
|
||||
// 5. Let minutesValue be duration.[[Minutes]].
|
||||
// 5. Let minutesDisplay be durationFormat.[[MinutesOptions]].[[Display]].
|
||||
auto minutes_value = duration.minutes();
|
||||
|
||||
// 6. Let minutesDisplay be durationFormat.[[MinutesDisplay]].
|
||||
auto minutes_display = duration_format.minutes_display();
|
||||
auto minutes_display = duration_format.minutes_options().display;
|
||||
|
||||
// 7. Let secondsValue be duration.[[Seconds]].
|
||||
Crypto::BigFraction seconds_value { duration.seconds() };
|
||||
|
@ -585,8 +582,8 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
|
|||
seconds_value = seconds_value + compute_fractional_digits(duration_format, duration);
|
||||
}
|
||||
|
||||
// 9. Let secondsDisplay be durationFormat.[[SecondsDisplay]].
|
||||
auto seconds_display = duration_format.seconds_display();
|
||||
// 9. Let secondsDisplay be durationFormat.[[SecondsOptions]].[[Display]].
|
||||
auto seconds_display = duration_format.seconds_options().display;
|
||||
|
||||
// 10. Let hoursFormatted be false.
|
||||
auto hours_formatted = false;
|
||||
|
@ -682,7 +679,15 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
|
|||
return numeric_parts_list;
|
||||
}
|
||||
|
||||
// 13.5.13 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts
|
||||
// 13.5.13 IsFractionalSecondUnitName ( unit ), https://tc39.es/ecma402/#sec-isfractionalsecondunitname
|
||||
bool is_fractional_second_unit_name(DurationFormat::Unit unit)
|
||||
{
|
||||
// 1. If unit is one of "milliseconds", "microseconds", or "nanoseconds", return true.
|
||||
// 2. Return false.
|
||||
return first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds);
|
||||
}
|
||||
|
||||
// 13.5.14 ListFormatParts ( durationFormat, partitionedPartsList ), https://tc39.es/ecma402/#sec-listformatparts
|
||||
Vector<DurationFormatPart> list_format_parts(VM& vm, DurationFormat const& duration_format, Vector<Vector<DurationFormatPart>>& partitioned_parts_list)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
@ -773,7 +778,7 @@ Vector<DurationFormatPart> list_format_parts(VM& vm, DurationFormat const& durat
|
|||
return flattened_parts_list;
|
||||
}
|
||||
|
||||
// 13.5.14 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern
|
||||
// 13.5.15 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/ecma402/#sec-partitiondurationformatpattern
|
||||
// 15.9.8 PartitionDurationFormatPattern ( durationFormat, duration ), https://tc39.es/proposal-temporal/#sec-formatnumericunits
|
||||
Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFormat const& duration_format, Temporal::Duration const& duration)
|
||||
{
|
||||
|
@ -788,23 +793,25 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
|
|||
// 3. Let numericUnitFound be false.
|
||||
auto numeric_unit_found = false;
|
||||
|
||||
// 4. While numericUnitFound is false, repeat for each row in Table 23 in table order, except the header row:
|
||||
// 4. While numericUnitFound is false, repeat for each row in Table 24 in table order, except the header row:
|
||||
for (size_t i = 0; !numeric_unit_found && i < duration_instances_components.size(); ++i) {
|
||||
auto const& duration_instances_component = duration_instances_components[i];
|
||||
|
||||
// a. Let value be the value of duration's field whose name is the Value Field value of the current row.
|
||||
Crypto::BigFraction value { (duration.*duration_instances_component.value_slot)() };
|
||||
|
||||
// b. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row.
|
||||
auto style = (duration_format.*duration_instances_component.get_style_slot)();
|
||||
// b. Let unitOptions be the value of durationFormat's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. Let style be unitOptions.[[Style]].
|
||||
// d. Let display be unitOptions.[[Display]].
|
||||
auto [style, display] = (duration_format.*duration_instances_component.get_internal_slot)();
|
||||
|
||||
// c. Let display be the value of durationFormat's internal slot whose name is the Display Slot value of the current row.
|
||||
auto display = (duration_format.*duration_instances_component.get_display_slot)();
|
||||
|
||||
// d. Let unit be the Unit value of the current row.
|
||||
// e. Let unit be the Unit value of the current row.
|
||||
auto unit = duration_instances_component.unit;
|
||||
|
||||
// e. If style is "numeric" or "2-digit", then
|
||||
// f. Let numberFormatUnit be the NumberFormat Unit value of the current row.
|
||||
auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit);
|
||||
|
||||
// g. If style is "numeric" or "2-digit", then
|
||||
if (style == DurationFormat::ValueStyle::Numeric || style == DurationFormat::ValueStyle::TwoDigit) {
|
||||
// i. Let numericPartsList be FormatNumericUnits(durationFormat, duration, unit, signDisplayed).
|
||||
auto numeric_parts_list = format_numeric_units(vm, duration_format, duration, unit, sign_displayed);
|
||||
|
@ -816,13 +823,13 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
|
|||
// iii. Set numericUnitFound to true.
|
||||
numeric_unit_found = true;
|
||||
}
|
||||
// f. Else,
|
||||
// h. Else,
|
||||
else {
|
||||
// i. Let nfOpts be OrdinaryObjectCreate(null).
|
||||
auto number_format_options = Object::create(realm, nullptr);
|
||||
|
||||
// ii. If unit is one of "seconds", "milliseconds", or "microseconds", and NextUnitFractional(durationFormat, unit) is true, then
|
||||
if (first_is_one_of(unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds) && next_unit_fractional(duration_format, unit)) {
|
||||
// ii. If NextUnitFractional(durationFormat, unit) is true, then
|
||||
if (next_unit_fractional(duration_format, unit)) {
|
||||
// 1. Set value to value + ComputeFractionalDigits(durationFormat, duration).
|
||||
value = value + compute_fractional_digits(duration_format, duration);
|
||||
|
||||
|
@ -875,29 +882,26 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
|
|||
MUST(number_format_options->create_data_property_or_throw(vm.names.signDisplay, PrimitiveString::create(vm, "never"sv)));
|
||||
}
|
||||
|
||||
// 4. Let numberFormatUnit be the NumberFormat Unit value of the current row.
|
||||
auto const& number_format_unit = unit_to_number_format_property_key(vm, duration_instances_component.unit);
|
||||
|
||||
// 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit").
|
||||
// 3. Perform ! CreateDataPropertyOrThrow(nfOpts, "style", "unit").
|
||||
MUST(number_format_options->create_data_property_or_throw(vm.names.style, PrimitiveString::create(vm, "unit"sv)));
|
||||
|
||||
// 6. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit).
|
||||
// 4. Perform ! CreateDataPropertyOrThrow(nfOpts, "unit", numberFormatUnit).
|
||||
MUST(number_format_options->create_data_property_or_throw(vm.names.unit, PrimitiveString::create(vm, number_format_unit.as_string())));
|
||||
|
||||
// 7. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style).
|
||||
// 5. Perform ! CreateDataPropertyOrThrow(nfOpts, "unitDisplay", style).
|
||||
auto locale_style = Unicode::style_to_string(static_cast<Unicode::Style>(style));
|
||||
MUST(number_format_options->create_data_property_or_throw(vm.names.unitDisplay, PrimitiveString::create(vm, locale_style)));
|
||||
|
||||
// 8. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »).
|
||||
// 6. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »).
|
||||
auto number_format = construct_number_format(vm, duration_format, number_format_options);
|
||||
|
||||
// 9. Let parts be PartitionNumberPattern(nf, value).
|
||||
// 7. Let parts be PartitionNumberPattern(nf, value).
|
||||
auto parts = partition_number_pattern(number_format, value_mv);
|
||||
|
||||
// 10. Let list be a new empty List.
|
||||
// 8. Let list be a new empty List.
|
||||
Vector<DurationFormatPart> list;
|
||||
|
||||
// 11. For each Record { [[Type]], [[Value]] } part of parts, do
|
||||
// 10. For each Record { [[Type]], [[Value]] } part of parts, do
|
||||
list.ensure_capacity(parts.size());
|
||||
|
||||
for (auto& part : parts) {
|
||||
|
@ -905,7 +909,7 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
|
|||
list.unchecked_append({ .type = part.type, .value = move(part.value), .unit = number_format_unit.as_string() });
|
||||
}
|
||||
|
||||
// 12. Append list to result.
|
||||
// 11. Append list to result.
|
||||
result.append(list);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,12 @@ public:
|
|||
Nanoseconds,
|
||||
};
|
||||
|
||||
// 13.5.6.1 Duration Unit Options Records, https://tc39.es/ecma402/#sec-durationformat-unit-options-record
|
||||
struct DurationUnitOptions {
|
||||
ValueStyle style { ValueStyle::Long };
|
||||
Display display { Display::Auto };
|
||||
};
|
||||
|
||||
static constexpr auto relevant_extension_keys()
|
||||
{
|
||||
// 13.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DurationFormat-internal-slots
|
||||
|
@ -91,85 +97,35 @@ public:
|
|||
Style style() const { return m_style; }
|
||||
StringView style_string() const { return style_to_string(m_style); }
|
||||
|
||||
void set_years_style(ValueStyle years_style) { m_years_style = years_style; }
|
||||
ValueStyle years_style() const { return m_years_style; }
|
||||
StringView years_style_string() const { return value_style_to_string(m_years_style); }
|
||||
void set_years_options(DurationUnitOptions years_options) { m_years_options = years_options; }
|
||||
DurationUnitOptions years_options() const { return m_years_options; }
|
||||
|
||||
void set_years_display(Display years_display) { m_years_display = years_display; }
|
||||
Display years_display() const { return m_years_display; }
|
||||
StringView years_display_string() const { return display_to_string(m_years_display); }
|
||||
void set_months_options(DurationUnitOptions months_options) { m_months_options = months_options; }
|
||||
DurationUnitOptions months_options() const { return m_months_options; }
|
||||
|
||||
void set_months_style(ValueStyle months_style) { m_months_style = months_style; }
|
||||
ValueStyle months_style() const { return m_months_style; }
|
||||
StringView months_style_string() const { return value_style_to_string(m_months_style); }
|
||||
void set_weeks_options(DurationUnitOptions weeks_options) { m_weeks_options = weeks_options; }
|
||||
DurationUnitOptions weeks_options() const { return m_weeks_options; }
|
||||
|
||||
void set_months_display(Display months_display) { m_months_display = months_display; }
|
||||
Display months_display() const { return m_months_display; }
|
||||
StringView months_display_string() const { return display_to_string(m_months_display); }
|
||||
void set_days_options(DurationUnitOptions days_options) { m_days_options = days_options; }
|
||||
DurationUnitOptions days_options() const { return m_days_options; }
|
||||
|
||||
void set_weeks_style(ValueStyle weeks_style) { m_weeks_style = weeks_style; }
|
||||
ValueStyle weeks_style() const { return m_weeks_style; }
|
||||
StringView weeks_style_string() const { return value_style_to_string(m_weeks_style); }
|
||||
void set_hours_options(DurationUnitOptions hours_options) { m_hours_options = hours_options; }
|
||||
DurationUnitOptions hours_options() const { return m_hours_options; }
|
||||
|
||||
void set_weeks_display(Display weeks_display) { m_weeks_display = weeks_display; }
|
||||
Display weeks_display() const { return m_weeks_display; }
|
||||
StringView weeks_display_string() const { return display_to_string(m_weeks_display); }
|
||||
void set_minutes_options(DurationUnitOptions minutes_options) { m_minutes_options = minutes_options; }
|
||||
DurationUnitOptions minutes_options() const { return m_minutes_options; }
|
||||
|
||||
void set_days_style(ValueStyle days_style) { m_days_style = days_style; }
|
||||
ValueStyle days_style() const { return m_days_style; }
|
||||
StringView days_style_string() const { return value_style_to_string(m_days_style); }
|
||||
void set_seconds_options(DurationUnitOptions seconds_options) { m_seconds_options = seconds_options; }
|
||||
DurationUnitOptions seconds_options() const { return m_seconds_options; }
|
||||
|
||||
void set_days_display(Display days_display) { m_days_display = days_display; }
|
||||
Display days_display() const { return m_days_display; }
|
||||
StringView days_display_string() const { return display_to_string(m_days_display); }
|
||||
void set_milliseconds_options(DurationUnitOptions milliseconds_options) { m_milliseconds_options = milliseconds_options; }
|
||||
DurationUnitOptions milliseconds_options() const { return m_milliseconds_options; }
|
||||
|
||||
void set_hours_style(ValueStyle hours_style) { m_hours_style = hours_style; }
|
||||
ValueStyle hours_style() const { return m_hours_style; }
|
||||
StringView hours_style_string() const { return value_style_to_string(m_hours_style); }
|
||||
void set_microseconds_options(DurationUnitOptions microseconds_options) { m_microseconds_options = microseconds_options; }
|
||||
DurationUnitOptions microseconds_options() const { return m_microseconds_options; }
|
||||
|
||||
void set_hours_display(Display hours_display) { m_hours_display = hours_display; }
|
||||
Display hours_display() const { return m_hours_display; }
|
||||
StringView hours_display_string() const { return display_to_string(m_hours_display); }
|
||||
|
||||
void set_minutes_style(ValueStyle minutes_style) { m_minutes_style = minutes_style; }
|
||||
ValueStyle minutes_style() const { return m_minutes_style; }
|
||||
StringView minutes_style_string() const { return value_style_to_string(m_minutes_style); }
|
||||
|
||||
void set_minutes_display(Display minutes_display) { m_minutes_display = minutes_display; }
|
||||
Display minutes_display() const { return m_minutes_display; }
|
||||
StringView minutes_display_string() const { return display_to_string(m_minutes_display); }
|
||||
|
||||
void set_seconds_style(ValueStyle seconds_style) { m_seconds_style = seconds_style; }
|
||||
ValueStyle seconds_style() const { return m_seconds_style; }
|
||||
StringView seconds_style_string() const { return value_style_to_string(m_seconds_style); }
|
||||
|
||||
void set_seconds_display(Display seconds_display) { m_seconds_display = seconds_display; }
|
||||
Display seconds_display() const { return m_seconds_display; }
|
||||
StringView seconds_display_string() const { return display_to_string(m_seconds_display); }
|
||||
|
||||
void set_milliseconds_style(ValueStyle milliseconds_style) { m_milliseconds_style = milliseconds_style; }
|
||||
ValueStyle milliseconds_style() const { return m_milliseconds_style; }
|
||||
StringView milliseconds_style_string() const { return value_style_to_string(m_milliseconds_style); }
|
||||
|
||||
void set_milliseconds_display(Display milliseconds_display) { m_milliseconds_display = milliseconds_display; }
|
||||
Display milliseconds_display() const { return m_milliseconds_display; }
|
||||
StringView milliseconds_display_string() const { return display_to_string(m_milliseconds_display); }
|
||||
|
||||
void set_microseconds_style(ValueStyle microseconds_style) { m_microseconds_style = microseconds_style; }
|
||||
ValueStyle microseconds_style() const { return m_microseconds_style; }
|
||||
StringView microseconds_style_string() const { return value_style_to_string(m_microseconds_style); }
|
||||
|
||||
void set_microseconds_display(Display microseconds_display) { m_microseconds_display = microseconds_display; }
|
||||
Display microseconds_display() const { return m_microseconds_display; }
|
||||
StringView microseconds_display_string() const { return display_to_string(m_microseconds_display); }
|
||||
|
||||
void set_nanoseconds_style(ValueStyle nanoseconds_style) { m_nanoseconds_style = nanoseconds_style; }
|
||||
ValueStyle nanoseconds_style() const { return m_nanoseconds_style; }
|
||||
StringView nanoseconds_style_string() const { return value_style_to_string(m_nanoseconds_style); }
|
||||
|
||||
void set_nanoseconds_display(Display nanoseconds_display) { m_nanoseconds_display = nanoseconds_display; }
|
||||
Display nanoseconds_display() const { return m_nanoseconds_display; }
|
||||
StringView nanoseconds_display_string() const { return display_to_string(m_nanoseconds_display); }
|
||||
void set_nanoseconds_options(DurationUnitOptions nanoseconds_options) { m_nanoseconds_options = nanoseconds_options; }
|
||||
DurationUnitOptions nanoseconds_options() const { return m_nanoseconds_options; }
|
||||
|
||||
void set_fractional_digits(Optional<u8> fractional_digits) { m_fractional_digits = move(fractional_digits); }
|
||||
bool has_fractional_digits() const { return m_fractional_digits.has_value(); }
|
||||
|
@ -183,78 +139,62 @@ private:
|
|||
String m_hour_minute_separator; // [[HourMinutesSeparator]]
|
||||
String m_minute_second_separator; // [[MinutesSecondsSeparator]]
|
||||
|
||||
Style m_style { Style::Long }; // [[Style]]
|
||||
ValueStyle m_years_style { ValueStyle::Long }; // [[YearsStyle]]
|
||||
Display m_years_display { Display::Auto }; // [[YearsDisplay]]
|
||||
ValueStyle m_months_style { ValueStyle::Long }; // [[MonthsStyle]]
|
||||
Display m_months_display { Display::Auto }; // [[MonthsDisplay]]
|
||||
ValueStyle m_weeks_style { ValueStyle::Long }; // [[WeeksStyle]]
|
||||
Display m_weeks_display { Display::Auto }; // [[WeeksDisplay]]
|
||||
ValueStyle m_days_style { ValueStyle::Long }; // [[DaysStyle]]
|
||||
Display m_days_display { Display::Auto }; // [[DaysDisplay]]
|
||||
ValueStyle m_hours_style { ValueStyle::Long }; // [[HoursStyle]]
|
||||
Display m_hours_display { Display::Auto }; // [[HoursDisplay]]
|
||||
ValueStyle m_minutes_style { ValueStyle::Long }; // [[MinutesStyle]]
|
||||
Display m_minutes_display { Display::Auto }; // [[MinutesDisplay]]
|
||||
ValueStyle m_seconds_style { ValueStyle::Long }; // [[SecondsStyle]]
|
||||
Display m_seconds_display { Display::Auto }; // [[SecondsDisplay]]
|
||||
ValueStyle m_milliseconds_style { ValueStyle::Long }; // [[MillisecondsStyle]]
|
||||
Display m_milliseconds_display { Display::Auto }; // [[MillisecondsDisplay]]
|
||||
ValueStyle m_microseconds_style { ValueStyle::Long }; // [[MicrosecondsStyle]]
|
||||
Display m_microseconds_display { Display::Auto }; // [[MicrosecondsDisplay]]
|
||||
ValueStyle m_nanoseconds_style { ValueStyle::Long }; // [[NanosecondsStyle]]
|
||||
Display m_nanoseconds_display { Display::Auto }; // [[NanosecondsDisplay]]
|
||||
Optional<u8> m_fractional_digits; // [[FractionalDigits]]
|
||||
Style m_style { Style::Long }; // [[Style]]
|
||||
DurationUnitOptions m_years_options; // [[YearsOptions]]
|
||||
DurationUnitOptions m_months_options; // [[MonthsOptions]]
|
||||
DurationUnitOptions m_weeks_options; // [[WeeksOptions]]
|
||||
DurationUnitOptions m_days_options; // [[DaysOptions]]
|
||||
DurationUnitOptions m_hours_options; // [[HoursOptions]]
|
||||
DurationUnitOptions m_minutes_options; // [[MinutesOptions]]
|
||||
DurationUnitOptions m_seconds_options; // [[SecondsOptions]]
|
||||
DurationUnitOptions m_milliseconds_options; // [[MillisecondsOptions]]
|
||||
DurationUnitOptions m_microseconds_options; // [[MicrosecondsOptions]]
|
||||
DurationUnitOptions m_nanoseconds_options; // [[NanosecondsOptions]]
|
||||
Optional<u8> m_fractional_digits; // [[FractionalDigits]]
|
||||
};
|
||||
|
||||
struct DurationInstanceComponent {
|
||||
double (Temporal::Duration::*value_slot)() const;
|
||||
DurationFormat::ValueStyle (DurationFormat::*get_style_slot)() const;
|
||||
void (DurationFormat::*set_style_slot)(DurationFormat::ValueStyle);
|
||||
DurationFormat::Display (DurationFormat::*get_display_slot)() const;
|
||||
void (DurationFormat::*set_display_slot)(DurationFormat::Display);
|
||||
DurationFormat::DurationUnitOptions (DurationFormat::*get_internal_slot)() const;
|
||||
void (DurationFormat::*set_internal_slot)(DurationFormat::DurationUnitOptions);
|
||||
DurationFormat::Unit unit;
|
||||
ReadonlySpan<StringView> values;
|
||||
ReadonlySpan<StringView> styles;
|
||||
DurationFormat::ValueStyle digital_default;
|
||||
};
|
||||
|
||||
// Table 20: Internal slots and property names of DurationFormat instances relevant to Intl.DurationFormat constructor, https://tc39.es/ecma402/#table-durationformat
|
||||
// Table 23: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern
|
||||
static constexpr auto date_values = AK::Array { "long"sv, "short"sv, "narrow"sv };
|
||||
static constexpr auto time_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv };
|
||||
static constexpr auto sub_second_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv };
|
||||
// Table 24: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/ecma402/#table-partition-duration-format-pattern
|
||||
static constexpr auto date_styles = AK::Array { "long"sv, "short"sv, "narrow"sv };
|
||||
static constexpr auto time_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv };
|
||||
static constexpr auto sub_second_styles = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv };
|
||||
|
||||
static constexpr auto duration_instances_components = to_array<DurationInstanceComponent>({
|
||||
{ &Temporal::Duration::years, &DurationFormat::years_style, &DurationFormat::set_years_style, &DurationFormat::years_display, &DurationFormat::set_years_display, DurationFormat::Unit::Years, date_values, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::months, &DurationFormat::months_style, &DurationFormat::set_months_style, &DurationFormat::months_display, &DurationFormat::set_months_display, DurationFormat::Unit::Months, date_values, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::weeks, &DurationFormat::weeks_style, &DurationFormat::set_weeks_style, &DurationFormat::weeks_display, &DurationFormat::set_weeks_display, DurationFormat::Unit::Weeks, date_values, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::days, &DurationFormat::days_style, &DurationFormat::set_days_style, &DurationFormat::days_display, &DurationFormat::set_days_display, DurationFormat::Unit::Days, date_values, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::hours, &DurationFormat::hours_style, &DurationFormat::set_hours_style, &DurationFormat::hours_display, &DurationFormat::set_hours_display, DurationFormat::Unit::Hours, time_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::minutes, &DurationFormat::minutes_style, &DurationFormat::set_minutes_style, &DurationFormat::minutes_display, &DurationFormat::set_minutes_display, DurationFormat::Unit::Minutes, time_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::seconds, &DurationFormat::seconds_style, &DurationFormat::set_seconds_style, &DurationFormat::seconds_display, &DurationFormat::set_seconds_display, DurationFormat::Unit::Seconds, time_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_style, &DurationFormat::set_milliseconds_style, &DurationFormat::milliseconds_display, &DurationFormat::set_milliseconds_display, DurationFormat::Unit::Milliseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::microseconds, &DurationFormat::microseconds_style, &DurationFormat::set_microseconds_style, &DurationFormat::microseconds_display, &DurationFormat::set_microseconds_display, DurationFormat::Unit::Microseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_style, &DurationFormat::set_nanoseconds_style, &DurationFormat::nanoseconds_display, &DurationFormat::set_nanoseconds_display, DurationFormat::Unit::Nanoseconds, sub_second_values, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::years, &DurationFormat::years_options, &DurationFormat::set_years_options, DurationFormat::Unit::Years, date_styles, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::months, &DurationFormat::months_options, &DurationFormat::set_months_options, DurationFormat::Unit::Months, date_styles, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::weeks, &DurationFormat::weeks_options, &DurationFormat::set_weeks_options, DurationFormat::Unit::Weeks, date_styles, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::days, &DurationFormat::days_options, &DurationFormat::set_days_options, DurationFormat::Unit::Days, date_styles, DurationFormat::ValueStyle::Short },
|
||||
{ &Temporal::Duration::hours, &DurationFormat::hours_options, &DurationFormat::set_hours_options, DurationFormat::Unit::Hours, time_styles, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::minutes, &DurationFormat::minutes_options, &DurationFormat::set_minutes_options, DurationFormat::Unit::Minutes, time_styles, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::seconds, &DurationFormat::seconds_options, &DurationFormat::set_seconds_options, DurationFormat::Unit::Seconds, time_styles, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::milliseconds, &DurationFormat::milliseconds_options, &DurationFormat::set_milliseconds_options, DurationFormat::Unit::Milliseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::microseconds, &DurationFormat::microseconds_options, &DurationFormat::set_microseconds_options, DurationFormat::Unit::Microseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
|
||||
{ &Temporal::Duration::nanoseconds, &DurationFormat::nanoseconds_options, &DurationFormat::set_nanoseconds_options, DurationFormat::Unit::Nanoseconds, sub_second_styles, DurationFormat::ValueStyle::Numeric },
|
||||
});
|
||||
|
||||
struct DurationUnitOptions {
|
||||
DurationFormat::ValueStyle style;
|
||||
DurationFormat::Display display;
|
||||
};
|
||||
|
||||
struct DurationFormatPart {
|
||||
StringView type;
|
||||
String value;
|
||||
StringView unit;
|
||||
};
|
||||
|
||||
ThrowCompletionOr<DurationUnitOptions> get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours);
|
||||
ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan<StringView> styles_list, DurationFormat::ValueStyle digital_base, Optional<DurationFormat::ValueStyle> previous_style, bool two_digit_hours);
|
||||
Crypto::BigFraction compute_fractional_digits(DurationFormat const&, Temporal::Duration const&);
|
||||
bool next_unit_fractional(DurationFormat const&, DurationFormat::Unit unit);
|
||||
Vector<DurationFormatPart> format_numeric_hours(VM&, DurationFormat const&, MathematicalValue const& hours_value, bool sign_displayed);
|
||||
Vector<DurationFormatPart> format_numeric_minutes(VM&, DurationFormat const&, MathematicalValue const& minutes_value, bool hours_displayed, bool sign_displayed);
|
||||
Vector<DurationFormatPart> format_numeric_seconds(VM&, DurationFormat const&, MathematicalValue const& seconds_value, bool minutes_displayed, bool sign_displayed);
|
||||
Vector<DurationFormatPart> format_numeric_units(VM&, DurationFormat const&, Temporal::Duration const&, DurationFormat::Unit first_numeric_unit, bool sign_displayed);
|
||||
bool is_fractional_second_unit_name(DurationFormat::Unit);
|
||||
Vector<DurationFormatPart> list_format_parts(VM&, DurationFormat const&, Vector<Vector<DurationFormatPart>>& partitioned_parts_list);
|
||||
Vector<DurationFormatPart> partition_duration_format_pattern(VM&, DurationFormat const&, Temporal::Duration const&);
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
|
|||
auto locales = vm.argument(0);
|
||||
auto options_value = vm.argument(1);
|
||||
|
||||
// 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]], [[WeeksStyle]], [[WeeksDisplay]], [[DaysStyle]], [[DaysDisplay]], [[HoursStyle]], [[HoursDisplay]], [[MinutesStyle]], [[MinutesDisplay]], [[SecondsStyle]], [[SecondsDisplay]], [[MillisecondsStyle]], [[MillisecondsDisplay]], [[MicrosecondsStyle]], [[MicrosecondsDisplay]], [[NanosecondsStyle]], [[NanosecondsDisplay]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »).
|
||||
// 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[NumberingSystem]], [[Style]], [[YearsOptions]], [[MonthsOptions]], [[WeeksOptions]], [[DaysOptions]], [[HoursOptions]], [[MinutesOptions]], [[SecondsOptions]], [[MillisecondsOptions]], [[MicrosecondsOptions]], [[NanosecondsOptions]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »).
|
||||
auto duration_format = TRY(ordinary_create_from_constructor<DurationFormat>(vm, new_target, &Intrinsics::intl_duration_format_prototype));
|
||||
|
||||
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
||||
|
@ -89,7 +89,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
|
|||
// 11. Let resolvedLocaleData be r.[[LocaleData]].
|
||||
|
||||
// 12. Let digitalFormat be resolvedLocaleData.[[DigitalFormat]].
|
||||
auto digital_format = Unicode::digital_format(duration_format->locale());
|
||||
auto digital_format = Unicode::digital_format(result.icu_locale);
|
||||
|
||||
// 13. Set durationFormat.[[HourMinuteSeparator]] to digitalFormat.[[HourMinuteSeparator]].
|
||||
duration_format->set_hour_minute_separator(move(digital_format.hours_minutes_separator));
|
||||
|
@ -112,31 +112,25 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
|
|||
|
||||
// 19. For each row of Table 20, except the header row, in table order, do
|
||||
for (auto const& duration_instances_component : duration_instances_components) {
|
||||
// a. Let styleSlot be the Style Slot value of the current row.
|
||||
auto style_slot = duration_instances_component.set_style_slot;
|
||||
// a. Let slot be the Internal Slot value of the current row.
|
||||
auto slot = duration_instances_component.set_internal_slot;
|
||||
|
||||
// b. Let displaySlot be the Display Slot value of the current row.
|
||||
auto display_slot = duration_instances_component.set_display_slot;
|
||||
|
||||
// c. Let unit be the Unit value of the current row.
|
||||
// b. Let unit be the Unit value of the current row.
|
||||
auto unit = duration_instances_component.unit;
|
||||
|
||||
// d. Let valueList be the Values value of the current row.
|
||||
auto value_list = duration_instances_component.values;
|
||||
// c. Let styles be the Styles value of the current row.
|
||||
auto styles = duration_instances_component.styles;
|
||||
|
||||
// e. Let digitalBase be the Digital Default value of the current row.
|
||||
// d. Let digitalBase be the Digital Default value of the current row.
|
||||
auto digital_base = duration_instances_component.digital_default;
|
||||
|
||||
// f. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, valueList, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]).
|
||||
auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), value_list, digital_base, previous_style, digital_format.uses_two_digit_hours));
|
||||
// e. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, styles, digitalBase, prevStyle, digitalFormat.[[TwoDigitHours]]).
|
||||
auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style(), styles, digital_base, previous_style, digital_format.uses_two_digit_hours));
|
||||
|
||||
// g. Set the value of the styleSlot slot of durationFormat to unitOptions.[[Style]].
|
||||
(duration_format->*style_slot)(unit_options.style);
|
||||
// f. Set the value of durationFormat's internal slot whose name is slot to unitOptions.
|
||||
(duration_format->*slot)(unit_options);
|
||||
|
||||
// h. Set the value of the displaySlot slot of durationFormat to unitOptions.[[Display]].
|
||||
(duration_format->*display_slot)(unit_options.display);
|
||||
|
||||
// i. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then
|
||||
// g. If unit is one of "hours", "minutes", "seconds", "milliseconds", or "microseconds", then
|
||||
if (first_is_one_of(unit, DurationFormat::Unit::Hours, DurationFormat::Unit::Minutes, DurationFormat::Unit::Seconds, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds)) {
|
||||
// i. Set prevStyle to unitOptions.[[Style]].
|
||||
previous_style = unit_options.style;
|
||||
|
|
|
@ -50,49 +50,57 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::resolved_options)
|
|||
auto options = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
|
||||
// 4. For each row of Table 21, except the header row, in table order, do
|
||||
auto create_option = [&](PropertyKey const& property, StringView value) {
|
||||
auto create_option = [&]<typename T>(PropertyKey const& property, Optional<PropertyKey const&> display_property, T value) {
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of df's internal slot whose name is the Internal Slot value of the current row.
|
||||
|
||||
// c. If p is "fractionalDigits", then
|
||||
// i. If v is not undefined, perform ! CreateDataPropertyOrThrow(options, p, 𝔽(v)).
|
||||
// NOTE: This case is handled separately below.
|
||||
// c. If v is not undefined, then
|
||||
// i. If there is a Conversion value in the current row, let conversion be that value; else let conversion be empty.
|
||||
// ii. If conversion is number, then
|
||||
// 1. Set v to 𝔽(v).
|
||||
// NOTE: This case is for fractionalDigits and is handled separately below.
|
||||
|
||||
// d. Else,
|
||||
// i. Assert: v is not undefined.
|
||||
// ii. If v is "fractional", then
|
||||
if (value == "fractional"sv) {
|
||||
// 1. Assert: The Internal Slot value of the current row is [[MillisecondsStyle]], [[MicrosecondsStyle]], or [[NanosecondsStyle]] .
|
||||
// 2. Set v to "numeric".
|
||||
value = "numeric"sv;
|
||||
// iii. Else if conversion is not empty, then
|
||||
if constexpr (IsSame<T, DurationFormat::DurationUnitOptions>) {
|
||||
// 1. Assert: conversion is STYLE+DISPLAY and v is a Duration Unit Options Record.
|
||||
// 2. NOTE: v.[[Style]] will be represented with a property named p (a plural Temporal unit), then v.[[Display]] will be represented with a property whose name suffixes p with "Display".
|
||||
VERIFY(display_property.has_value());
|
||||
|
||||
// 3. Let style be v.[[Style]].
|
||||
auto style = value.style;
|
||||
|
||||
// 4. If style is "fractional", then
|
||||
if (style == DurationFormat::ValueStyle::Fractional) {
|
||||
// a. Assert: IsFractionalSecondUnitName(p) is true.
|
||||
// b. Set style to "numeric".
|
||||
style = DurationFormat::ValueStyle::Numeric;
|
||||
}
|
||||
|
||||
// 5. Perform ! CreateDataPropertyOrThrow(options, p, style).
|
||||
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, DurationFormat::value_style_to_string(style))));
|
||||
|
||||
// 6. Set p to the string-concatenation of p and "Display".
|
||||
// 7. Set v to v.[[Display]].
|
||||
MUST(options->create_data_property_or_throw(*display_property, PrimitiveString::create(vm, DurationFormat::display_to_string(value.display))));
|
||||
} else {
|
||||
// iv. Perform ! CreateDataPropertyOrThrow(options, p, v).
|
||||
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, move(value))));
|
||||
}
|
||||
// iii. Perform ! CreateDataPropertyOrThrow(options, p, v).
|
||||
MUST(options->create_data_property_or_throw(property, PrimitiveString::create(vm, value)));
|
||||
};
|
||||
|
||||
create_option(vm.names.locale, duration_format->locale());
|
||||
create_option(vm.names.numberingSystem, duration_format->numbering_system());
|
||||
create_option(vm.names.style, duration_format->style_string());
|
||||
create_option(vm.names.years, duration_format->years_style_string());
|
||||
create_option(vm.names.yearsDisplay, duration_format->years_display_string());
|
||||
create_option(vm.names.months, duration_format->months_style_string());
|
||||
create_option(vm.names.monthsDisplay, duration_format->months_display_string());
|
||||
create_option(vm.names.weeks, duration_format->weeks_style_string());
|
||||
create_option(vm.names.weeksDisplay, duration_format->weeks_display_string());
|
||||
create_option(vm.names.days, duration_format->days_style_string());
|
||||
create_option(vm.names.daysDisplay, duration_format->days_display_string());
|
||||
create_option(vm.names.hours, duration_format->hours_style_string());
|
||||
create_option(vm.names.hoursDisplay, duration_format->hours_display_string());
|
||||
create_option(vm.names.minutes, duration_format->minutes_style_string());
|
||||
create_option(vm.names.minutesDisplay, duration_format->minutes_display_string());
|
||||
create_option(vm.names.seconds, duration_format->seconds_style_string());
|
||||
create_option(vm.names.secondsDisplay, duration_format->seconds_display_string());
|
||||
create_option(vm.names.milliseconds, duration_format->milliseconds_style_string());
|
||||
create_option(vm.names.millisecondsDisplay, duration_format->milliseconds_display_string());
|
||||
create_option(vm.names.microseconds, duration_format->microseconds_style_string());
|
||||
create_option(vm.names.microsecondsDisplay, duration_format->microseconds_display_string());
|
||||
create_option(vm.names.nanoseconds, duration_format->nanoseconds_style_string());
|
||||
create_option(vm.names.nanosecondsDisplay, duration_format->nanoseconds_display_string());
|
||||
create_option(vm.names.locale, {}, duration_format->locale());
|
||||
create_option(vm.names.numberingSystem, {}, duration_format->numbering_system());
|
||||
create_option(vm.names.style, {}, duration_format->style_string());
|
||||
create_option(vm.names.years, vm.names.yearsDisplay, duration_format->years_options());
|
||||
create_option(vm.names.months, vm.names.monthsDisplay, duration_format->months_options());
|
||||
create_option(vm.names.weeks, vm.names.weeksDisplay, duration_format->weeks_options());
|
||||
create_option(vm.names.days, vm.names.daysDisplay, duration_format->days_options());
|
||||
create_option(vm.names.hours, vm.names.hoursDisplay, duration_format->hours_options());
|
||||
create_option(vm.names.minutes, vm.names.minutesDisplay, duration_format->minutes_options());
|
||||
create_option(vm.names.seconds, vm.names.secondsDisplay, duration_format->seconds_options());
|
||||
create_option(vm.names.milliseconds, vm.names.millisecondsDisplay, duration_format->milliseconds_options());
|
||||
create_option(vm.names.microseconds, vm.names.microsecondsDisplay, duration_format->microseconds_options());
|
||||
create_option(vm.names.nanoseconds, vm.names.nanosecondsDisplay, duration_format->nanoseconds_options());
|
||||
|
||||
if (duration_format->has_fractional_digits())
|
||||
MUST(options->create_data_property_or_throw(vm.names.fractionalDigits, Value(duration_format->fractional_digits())));
|
||||
|
|
|
@ -47,7 +47,7 @@ JS_DEFINE_NATIVE_FUNCTION(ListFormatPrototype::resolved_options)
|
|||
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
auto options = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
|
||||
// 4. For each row of Table 24, except the header row, in table order, do
|
||||
// 4. For each row of Table 25, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of lf's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. Assert: v is not undefined.
|
||||
|
|
|
@ -63,13 +63,13 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
// 5. Let opt be a new Record.
|
||||
LocaleOptions opt {};
|
||||
|
||||
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
|
||||
// 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit").
|
||||
auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv));
|
||||
|
||||
// 7. Set opt.[[localeMatcher]] to matcher.
|
||||
opt.locale_matcher = matcher;
|
||||
|
||||
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
|
||||
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined).
|
||||
auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {}));
|
||||
|
||||
// 9. If numberingSystem is not undefined, then
|
||||
|
@ -100,11 +100,17 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
// 16. Let style be numberFormat.[[Style]].
|
||||
auto style = number_format->style();
|
||||
|
||||
// 17. Let notation be ? GetOption(options, "notation", STRING, « "standard", "scientific", "engineering", "compact" », "standard").
|
||||
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
|
||||
|
||||
// 18. Set numberFormat.[[Notation]] to notation.
|
||||
number_format->set_notation(notation.as_string().utf8_string_view());
|
||||
|
||||
int default_min_fraction_digits = 0;
|
||||
int default_max_fraction_digits = 0;
|
||||
|
||||
// 17. If style is "currency", then
|
||||
if (style == Unicode::NumberFormatStyle::Currency) {
|
||||
// 19. If style is "currency" and notation is "standard", then
|
||||
if (style == Unicode::NumberFormatStyle::Currency && number_format->notation() == Unicode::Notation::Standard) {
|
||||
// a. Let currency be numberFormat.[[Currency]].
|
||||
auto const& currency = number_format->currency();
|
||||
|
||||
|
@ -117,7 +123,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
// d. Let mxfdDefault be cDigits.
|
||||
default_max_fraction_digits = digits;
|
||||
}
|
||||
// 18. Else,
|
||||
// 20. Else,
|
||||
else {
|
||||
// a. Let mnfdDefault be 0.
|
||||
default_min_fraction_digits = 0;
|
||||
|
@ -129,16 +135,10 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
default_max_fraction_digits = style == Unicode::NumberFormatStyle::Percent ? 0 : 3;
|
||||
}
|
||||
|
||||
// 19. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
|
||||
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
|
||||
|
||||
// 20. Set numberFormat.[[Notation]] to notation.
|
||||
number_format->set_notation(notation.as_string().utf8_string_view());
|
||||
|
||||
// 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
|
||||
TRY(set_number_format_digit_options(vm, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format->notation()));
|
||||
|
||||
// 22. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short").
|
||||
// 22. Let compactDisplay be ? GetOption(options, "compactDisplay", STRING, « "short", "long" », "short").
|
||||
auto compact_display = TRY(get_option(vm, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv));
|
||||
|
||||
// 23. Let defaultUseGrouping be "auto".
|
||||
|
@ -172,7 +172,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
// 29. Set numberFormat.[[UseGrouping]] to useGrouping.
|
||||
number_format->set_use_grouping(use_grouping);
|
||||
|
||||
// 30. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto").
|
||||
// 30. Let signDisplay be ? GetOption(options, "signDisplay", STRING, « "auto", "never", "always", "exceptZero", "negative" », "auto").
|
||||
auto sign_display = TRY(get_option(vm, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv));
|
||||
|
||||
// 31. Set numberFormat.[[SignDisplay]] to signDisplay.
|
||||
|
@ -184,8 +184,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
|
|||
|
||||
// Non-standard, create an ICU number formatter for this Intl object.
|
||||
auto formatter = Unicode::NumberFormat::create(
|
||||
number_format->locale(),
|
||||
number_format->numbering_system(),
|
||||
result.icu_locale,
|
||||
number_format->display_options(),
|
||||
number_format->rounding_options());
|
||||
number_format->set_formatter(move(formatter));
|
||||
|
@ -224,14 +223,14 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
if (!sanctioned_rounding_increments.span().contains_slow(*rounding_increment))
|
||||
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidRoundingIncrement, *rounding_increment);
|
||||
|
||||
// 9. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
|
||||
// 9. Let roundingMode be ? GetOption(options, "roundingMode", STRING, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
|
||||
auto rounding_mode = TRY(get_option(vm, options, vm.names.roundingMode, OptionType::String, { "ceil"sv, "floor"sv, "expand"sv, "trunc"sv, "halfCeil"sv, "halfFloor"sv, "halfExpand"sv, "halfTrunc"sv, "halfEven"sv }, "halfExpand"sv));
|
||||
|
||||
// 10. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto").
|
||||
// 10. Let roundingPriority be ? GetOption(options, "roundingPriority", STRING, « "auto", "morePrecision", "lessPrecision" », "auto").
|
||||
auto rounding_priority_option = TRY(get_option(vm, options, vm.names.roundingPriority, OptionType::String, { "auto"sv, "morePrecision"sv, "lessPrecision"sv }, "auto"sv));
|
||||
auto rounding_priority = rounding_priority_option.as_string().utf8_string_view();
|
||||
|
||||
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto").
|
||||
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", STRING, « "auto", "stripIfInteger" », "auto").
|
||||
auto trailing_zero_display = TRY(get_option(vm, options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv));
|
||||
|
||||
// 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions.
|
||||
|
@ -266,8 +265,8 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// a. Set needSd to hasSd.
|
||||
need_significant_digits = has_significant_digits;
|
||||
|
||||
// b. If hasSd is true, or hasFd is false and notation is "compact", then
|
||||
if (has_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) {
|
||||
// b. If needSd is true, or hasFd is false and notation is "compact", then
|
||||
if (need_significant_digits || (!has_fraction_digits && notation == Unicode::Notation::Compact)) {
|
||||
// i. Set needFd to false.
|
||||
need_fraction_digits = false;
|
||||
}
|
||||
|
@ -390,7 +389,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
if (intl_object.rounding_type() != Unicode::RoundingType::FractionDigits)
|
||||
return vm.throw_completion<TypeError>(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, intl_object.rounding_type_string());
|
||||
|
||||
// b. If intlObj.[[MaximumFractionDigits]] is not equal to intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
|
||||
// b. If intlObj.[[MaximumFractionDigits]] is not intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
|
||||
if (intl_object.max_fraction_digits() != intl_object.min_fraction_digits())
|
||||
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidRoundingIncrementForFractionDigits, *rounding_increment);
|
||||
}
|
||||
|
@ -402,13 +401,13 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 16.1.3 SetNumberFormatUnitOptions ( intlObj, options ), https://tc39.es/ecma402/#sec-setnumberformatunitoptions
|
||||
ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& intl_object, Object const& options)
|
||||
{
|
||||
// 1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal").
|
||||
// 1. Let style be ? GetOption(options, "style", STRING, « "decimal", "percent", "currency", "unit" », "decimal").
|
||||
auto style = TRY(get_option(vm, options, vm.names.style, OptionType::String, { "decimal"sv, "percent"sv, "currency"sv, "unit"sv }, "decimal"sv));
|
||||
|
||||
// 2. Set intlObj.[[Style]] to style.
|
||||
intl_object.set_style(style.as_string().utf8_string_view());
|
||||
|
||||
// 3. Let currency be ? GetOption(options, "currency", string, empty, undefined).
|
||||
// 3. Let currency be ? GetOption(options, "currency", STRING, EMPTY, undefined).
|
||||
auto currency = TRY(get_option(vm, options, vm.names.currency, OptionType::String, {}, Empty {}));
|
||||
|
||||
// 4. If currency is undefined, then
|
||||
|
@ -423,13 +422,13 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, currency, "currency"sv);
|
||||
}
|
||||
|
||||
// 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol").
|
||||
// 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", STRING, « "code", "symbol", "narrowSymbol", "name" », "symbol").
|
||||
auto currency_display = TRY(get_option(vm, options, vm.names.currencyDisplay, OptionType::String, { "code"sv, "symbol"sv, "narrowSymbol"sv, "name"sv }, "symbol"sv));
|
||||
|
||||
// 7. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard").
|
||||
// 7. Let currencySign be ? GetOption(options, "currencySign", STRING, « "standard", "accounting" », "standard").
|
||||
auto currency_sign = TRY(get_option(vm, options, vm.names.currencySign, OptionType::String, { "standard"sv, "accounting"sv }, "standard"sv));
|
||||
|
||||
// 8. Let unit be ? GetOption(options, "unit", string, empty, undefined).
|
||||
// 8. Let unit be ? GetOption(options, "unit", STRING, EMPTY, undefined).
|
||||
auto unit = TRY(get_option(vm, options, vm.names.unit, OptionType::String, {}, Empty {}));
|
||||
|
||||
// 9. If unit is undefined, then
|
||||
|
@ -444,7 +443,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, unit, "unit"sv);
|
||||
}
|
||||
|
||||
// 11. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short").
|
||||
// 11. Let unitDisplay be ? GetOption(options, "unitDisplay", STRING, « "short", "narrow", "long" », "short").
|
||||
auto unit_display = TRY(get_option(vm, options, vm.names.unitDisplay, OptionType::String, { "short"sv, "narrow"sv, "long"sv }, "short"sv));
|
||||
|
||||
// 12. If style is "currency", then
|
||||
|
|
|
@ -53,7 +53,7 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatPrototype::resolved_options)
|
|||
// 4. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
auto options = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
|
||||
// 5. For each row of Table 25, except the header row, in table order, do
|
||||
// 5. For each row of Table 26, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of nf's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. If v is not undefined, then
|
||||
|
|
|
@ -87,8 +87,7 @@ ThrowCompletionOr<GC::Ref<Object>> PluralRulesConstructor::construct(FunctionObj
|
|||
|
||||
// Non-standard, create an ICU number formatter for this Intl object.
|
||||
auto formatter = Unicode::NumberFormat::create(
|
||||
plural_rules->locale(),
|
||||
{},
|
||||
result.icu_locale,
|
||||
{},
|
||||
plural_rules->rounding_options());
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options)
|
|||
return PrimitiveString::create(vm, Unicode::plural_category_to_string(category));
|
||||
});
|
||||
|
||||
// 5. For each row of Table 29, except the header row, in table order, do
|
||||
// 5. For each row of Table 30, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. If p is "pluralCategories", then
|
||||
// i. Let v be CreateArrayFromList(pluralCategories).
|
||||
|
|
|
@ -63,13 +63,13 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
|
|||
// 5. Let opt be a new Record.
|
||||
LocaleOptions opt {};
|
||||
|
||||
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
|
||||
// 6. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit").
|
||||
auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, AK::Array { "lookup"sv, "best fit"sv }, "best fit"sv));
|
||||
|
||||
// 7. Set opt.[[LocaleMatcher]] to matcher.
|
||||
opt.locale_matcher = matcher;
|
||||
|
||||
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
|
||||
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined).
|
||||
auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {}));
|
||||
|
||||
// 9. If numberingSystem is not undefined, then
|
||||
|
@ -97,13 +97,13 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
|
|||
if (auto* resolved_numbering_system = result.nu.get_pointer<String>())
|
||||
relative_time_format->set_numbering_system(move(*resolved_numbering_system));
|
||||
|
||||
// 16. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
|
||||
// 16. Let style be ? GetOption(options, "style", STRING, « "long", "short", "narrow" », "long").
|
||||
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));
|
||||
|
||||
// 17. Set relativeTimeFormat.[[Style]] to style.
|
||||
relative_time_format->set_style(style.as_string().utf8_string_view());
|
||||
|
||||
// 18. Let numeric be ? GetOption(options, "numeric", string, « "always", "auto" », "always").
|
||||
// 18. Let numeric be ? GetOption(options, "numeric", STRING, « "always", "auto" », "always").
|
||||
auto numeric = TRY(get_option(vm, *options, vm.names.numeric, OptionType::String, { "always"sv, "auto"sv }, "always"sv));
|
||||
|
||||
// 19. Set relativeTimeFormat.[[Numeric]] to numeric.
|
||||
|
@ -112,7 +112,7 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
|
|||
// 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%Intl.NumberFormat%, « locale »).
|
||||
// 21. Let relativeTimeFormat.[[PluralRules]] be ! Construct(%Intl.PluralRules%, « locale »).
|
||||
auto formatter = Unicode::RelativeTimeFormat::create(
|
||||
relative_time_format->locale(),
|
||||
result.icu_locale,
|
||||
relative_time_format->style());
|
||||
relative_time_format->set_formatter(move(formatter));
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::resolved_options)
|
|||
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
auto options = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
|
||||
// 4. For each row of Table 30, except the header row, in table order, do
|
||||
// 4. For each row of Table 31, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of relativeTimeFormat's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. Assert: v is not undefined.
|
||||
|
|
|
@ -45,7 +45,7 @@ JS_DEFINE_NATIVE_FUNCTION(SegmenterPrototype::resolved_options)
|
|||
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
auto options = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
|
||||
// 4. For each row of Table 31, except the header row, in table order, do
|
||||
// 4. For each row of Table 32, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of segmenter's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. Assert: v is not undefined.
|
||||
|
|
|
@ -34,7 +34,7 @@ ThrowCompletionOr<Value> ModuleEnvironment::get_binding_value(VM& vm, Deprecated
|
|||
// a. Let M and N2 be the indirection values provided when this binding for N was created.
|
||||
|
||||
// b. Let targetEnv be M.[[Environment]].
|
||||
auto* target_env = indirect_binding->module->environment();
|
||||
auto target_env = indirect_binding->module->environment();
|
||||
|
||||
// c. If targetEnv is empty, throw a ReferenceError exception.
|
||||
if (!target_env)
|
||||
|
@ -97,11 +97,10 @@ Optional<ModuleEnvironment::BindingAndIndex> ModuleEnvironment::find_binding_and
|
|||
{
|
||||
auto* indirect_binding = get_indirect_binding(name);
|
||||
if (indirect_binding != nullptr) {
|
||||
auto* target_env = indirect_binding->module->environment();
|
||||
auto target_env = indirect_binding->module->environment();
|
||||
if (!target_env)
|
||||
return {};
|
||||
|
||||
VERIFY(is<ModuleEnvironment>(target_env));
|
||||
auto& target_module_environment = static_cast<ModuleEnvironment&>(*target_env);
|
||||
auto result = target_module_environment.find_binding_and_index(indirect_binding->binding_name);
|
||||
if (!result.has_value())
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/ModuleEnvironment.h>
|
||||
#include <LibJS/Runtime/ModuleNamespaceObject.h>
|
||||
|
||||
namespace JS {
|
||||
|
@ -173,7 +174,7 @@ ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const&
|
|||
}
|
||||
|
||||
// 10. Let targetEnv be targetModule.[[Environment]].
|
||||
auto* target_environment = target_module->environment();
|
||||
auto target_environment = target_module->environment();
|
||||
|
||||
// 11. If targetEnv is empty, throw a ReferenceError exception.
|
||||
if (!target_environment)
|
||||
|
|
|
@ -22,12 +22,6 @@ describe("errors", () => {
|
|||
BigInt.asIntN(1, "foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
|
||||
test("large allocation", () => {
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x4000000000000, 1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
|
@ -82,4 +76,23 @@ describe("correct behavior", () => {
|
|||
expect(BigInt.asIntN(128, -extremelyBigInt)).toBe(99061374399389259395070030194384019691n);
|
||||
expect(BigInt.asIntN(256, -extremelyBigInt)).toBe(-extremelyBigInt);
|
||||
});
|
||||
|
||||
test("large bit values", () => {
|
||||
expect(BigInt.asIntN(0x4000000000000, 1n)).toBe(1n);
|
||||
expect(BigInt.asIntN(0x8ffffffffffff, 1n)).toBe(1n);
|
||||
expect(BigInt.asIntN(2 ** 53 - 1, 2n)).toBe(2n);
|
||||
|
||||
// These incur large intermediate values that 00M. For now, ensure they don't crash
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x4000000000000, -1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x8ffffffffffff, -1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
|
||||
expect(() => {
|
||||
BigInt.asIntN(2 ** 53 - 1, -2n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,12 +22,6 @@ describe("errors", () => {
|
|||
BigInt.asUintN(1, "foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
|
||||
test("large allocation", () => {
|
||||
expect(() => {
|
||||
BigInt.asUintN(0x4000000000000, 1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
|
@ -80,4 +74,20 @@ describe("correct behavior", () => {
|
|||
115792089237316195423570861551898784396480861208851440582668460551124006183147n
|
||||
);
|
||||
});
|
||||
|
||||
test("large bit values", () => {
|
||||
const INDEX_MAX = 2 ** 53 - 1;
|
||||
const LAST_8_DIGITS = 10n ** 8n;
|
||||
|
||||
expect(BigInt.asUintN(0x400000000, 1n)).toBe(1n);
|
||||
expect(BigInt.asUintN(0x4000, -1n) % LAST_8_DIGITS).toBe(64066815n);
|
||||
|
||||
expect(BigInt.asUintN(0x400000000, 2n)).toBe(2n);
|
||||
expect(BigInt.asUintN(0x4000, -2n) % LAST_8_DIGITS).toBe(64066814n);
|
||||
|
||||
expect(BigInt.asUintN(INDEX_MAX, 2n)).toBe(2n);
|
||||
expect(() => {
|
||||
BigInt.asUintN(INDEX_MAX, -2n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -652,17 +652,17 @@ describe("Temporal objects", () => {
|
|||
|
||||
test("Temporal.PlainDate", () => {
|
||||
const plainDate = new Temporal.PlainDate(1989, 1, 23);
|
||||
expect(formatter.format(plainDate)).toBe("1/23/1989");
|
||||
expect(formatter.format(plainDate)).toBe("1989-01-23");
|
||||
});
|
||||
|
||||
test("Temporal.PlainYearMonth", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
|
||||
expect(formatter.format(plainYearMonth)).toBe("1/1989");
|
||||
expect(formatter.format(plainYearMonth)).toBe("1989-01");
|
||||
});
|
||||
|
||||
test("Temporal.PlainMonthDay", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
|
||||
expect(formatter.format(plainMonthDay)).toBe("1/23");
|
||||
expect(formatter.format(plainMonthDay)).toBe("01-23");
|
||||
});
|
||||
|
||||
test("Temporal.PlainTime", () => {
|
||||
|
@ -672,6 +672,6 @@ describe("Temporal objects", () => {
|
|||
|
||||
test("Temporal.Instant", () => {
|
||||
const instant = new Temporal.Instant(1732740069000000000n);
|
||||
expect(formatter.format(instant)).toBe("11/27/2024, 8:41:09 PM");
|
||||
expect(formatter.format(instant)).toBe("2024-11-27, 8:41:09 PM");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -322,19 +322,19 @@ describe("Temporal objects", () => {
|
|||
test("Temporal.PlainDate", () => {
|
||||
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
|
||||
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
|
||||
expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1/23/1989 – 11/27/2024");
|
||||
expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1989-01-23 – 2024-11-27");
|
||||
});
|
||||
|
||||
test("Temporal.PlainYearMonth", () => {
|
||||
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
|
||||
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
|
||||
expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1/1989 – 11/2024");
|
||||
expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1989-01 – 2024-11");
|
||||
});
|
||||
|
||||
test("Temporal.PlainMonthDay", () => {
|
||||
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
|
||||
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
|
||||
expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("1/23 – 11/27");
|
||||
expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("01-23 – 11-27");
|
||||
});
|
||||
|
||||
test("Temporal.PlainTime", () => {
|
||||
|
@ -347,7 +347,7 @@ describe("Temporal objects", () => {
|
|||
const instant1 = new Temporal.Instant(601546251000000000n);
|
||||
const instant2 = new Temporal.Instant(1732740069000000000n);
|
||||
expect(formatter.formatRange(instant1, instant2)).toBe(
|
||||
"1/23/1989, 8:10:51 AM – 11/27/2024, 8:41:09 PM"
|
||||
"1989-01-23, 8:10:51 AM – 2024-11-27, 8:41:09 PM"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -736,17 +736,17 @@ describe("Temporal objects", () => {
|
|||
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
|
||||
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
|
||||
expect(formatter.formatRangeToParts(plainDate1, plainDate2)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "month", value: "01", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " – ", source: "shared" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "27", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2024", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "day", value: "27", source: "endRange" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -754,13 +754,13 @@ describe("Temporal objects", () => {
|
|||
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
|
||||
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
|
||||
expect(formatter.formatRangeToParts(plainYearMonth1, plainYearMonth2)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "month", value: "01", source: "startRange" },
|
||||
{ type: "literal", value: " – ", source: "shared" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2024", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -768,12 +768,12 @@ describe("Temporal objects", () => {
|
|||
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
|
||||
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
|
||||
expect(formatter.formatRangeToParts(plainMonthDay1, plainMonthDay2)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "01", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " – ", source: "shared" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "day", value: "27", source: "endRange" },
|
||||
]);
|
||||
});
|
||||
|
@ -804,11 +804,11 @@ describe("Temporal objects", () => {
|
|||
const instant1 = new Temporal.Instant(601546251000000000n);
|
||||
const instant2 = new Temporal.Instant(1732740069000000000n);
|
||||
expect(formatter.formatRangeToParts(instant1, instant2)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "month", value: "01", source: "startRange" },
|
||||
{ type: "literal", value: "-", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "hour", value: "8", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
|
@ -818,11 +818,11 @@ describe("Temporal objects", () => {
|
|||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "dayPeriod", value: "AM", source: "startRange" },
|
||||
{ type: "literal", value: " – ", source: "shared" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "27", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2024", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "month", value: "11", source: "endRange" },
|
||||
{ type: "literal", value: "-", source: "endRange" },
|
||||
{ type: "day", value: "27", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "hour", value: "8", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
|
|
|
@ -350,28 +350,28 @@ describe("Temporal objects", () => {
|
|||
test("Temporal.PlainDate", () => {
|
||||
const plainDate = new Temporal.PlainDate(1989, 1, 23);
|
||||
expect(formatter.formatToParts(plainDate)).toEqual([
|
||||
{ type: "month", value: "1" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "day", value: "23" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "year", value: "1989" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "month", value: "01" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "day", value: "23" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("Temporal.PlainYearMonth", () => {
|
||||
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
|
||||
expect(formatter.formatToParts(plainYearMonth)).toEqual([
|
||||
{ type: "month", value: "1" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "year", value: "1989" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "month", value: "01" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("Temporal.PlainMonthDay", () => {
|
||||
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
|
||||
expect(formatter.formatToParts(plainMonthDay)).toEqual([
|
||||
{ type: "month", value: "1" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "month", value: "01" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "day", value: "23" },
|
||||
]);
|
||||
});
|
||||
|
@ -392,11 +392,11 @@ describe("Temporal objects", () => {
|
|||
test("Temporal.Instant", () => {
|
||||
const instant = new Temporal.Instant(1732740069000000000n);
|
||||
expect(formatter.formatToParts(instant)).toEqual([
|
||||
{ type: "month", value: "11" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "day", value: "27" },
|
||||
{ type: "literal", value: "/" },
|
||||
{ type: "year", value: "2024" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "month", value: "11" },
|
||||
{ type: "literal", value: "-" },
|
||||
{ type: "day", value: "27" },
|
||||
{ type: "literal", value: ", " },
|
||||
{ type: "hour", value: "8" },
|
||||
{ type: "literal", value: ":" },
|
||||
|
|
|
@ -95,6 +95,26 @@ describe("correct behavior", () => {
|
|||
expect(en2.resolvedOptions().maximumFractionDigits).toBe(2);
|
||||
});
|
||||
|
||||
test("currency digits are not used for non-standard notation", () => {
|
||||
const en1 = new Intl.NumberFormat("en", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
notation: "engineering",
|
||||
});
|
||||
expect(en1.resolvedOptions().style).toBe("currency");
|
||||
expect(en1.resolvedOptions().minimumFractionDigits).toBe(0);
|
||||
expect(en1.resolvedOptions().maximumFractionDigits).toBe(3);
|
||||
|
||||
const en2 = new Intl.NumberFormat("en", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
notation: "compact",
|
||||
});
|
||||
expect(en2.resolvedOptions().style).toBe("currency");
|
||||
expect(en2.resolvedOptions().minimumFractionDigits).toBe(0);
|
||||
expect(en2.resolvedOptions().maximumFractionDigits).toBe(0);
|
||||
});
|
||||
|
||||
test("other style options default to min fraction digits of 0 and max fraction digits of 0 or 3", () => {
|
||||
const en1 = new Intl.NumberFormat("en", { style: "decimal" });
|
||||
expect(en1.resolvedOptions().style).toBe("decimal");
|
||||
|
|
|
@ -129,6 +129,11 @@ describe("second", () => {
|
|||
|
||||
runTest("second", "narrow", "auto", en, ar, pl);
|
||||
});
|
||||
|
||||
test("numberingSystem set via locale options", () => {
|
||||
const formatter = new Intl.RelativeTimeFormat("en", { numberingSystem: "arab" });
|
||||
expect(formatter.format(1, "second")).toBe("in ١ second");
|
||||
});
|
||||
});
|
||||
|
||||
describe("minute", () => {
|
||||
|
|
12
Libraries/LibJS/Tests/builtins/Proxy/iterate-over-proxy.js
Normal file
12
Libraries/LibJS/Tests/builtins/Proxy/iterate-over-proxy.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
test("iterate over bogus proxy", () => {
|
||||
expect(() => {
|
||||
let proxy = new Proxy([123], {
|
||||
getOwnPropertyDescriptor: function (p) {
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
for (const p in proxy) {
|
||||
}
|
||||
}).toThrow();
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
test("Proxied constructor should handle argument_buffer reallocation during prototype get()", () => {
|
||||
function foo() {}
|
||||
|
||||
let handler = {
|
||||
get() {
|
||||
// prettier-ignore
|
||||
foo(
|
||||
// make extra sure we trigger a reallocation
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41
|
||||
);
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
function Construct() {
|
||||
// later use dangling pointer
|
||||
console.log(arguments);
|
||||
}
|
||||
|
||||
let ConstructProxy = new Proxy(Construct, handler);
|
||||
|
||||
new ConstructProxy(0x1);
|
||||
});
|
|
@ -218,6 +218,15 @@ ALWAYS_INLINE ExecutionResult OpCode_FailForks::execute(MatchInput const& input,
|
|||
return ExecutionResult::Failed_ExecuteLowPrioForks;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ExecutionResult OpCode_PopSaved::execute(MatchInput const& input, MatchState&) const
|
||||
{
|
||||
if (input.saved_positions.is_empty() || input.saved_code_unit_positions.is_empty())
|
||||
return ExecutionResult::Failed_ExecuteLowPrioForks;
|
||||
input.saved_positions.take_last();
|
||||
input.saved_code_unit_positions.take_last();
|
||||
return ExecutionResult::Failed_ExecuteLowPrioForks;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ExecutionResult OpCode_Jump::execute(MatchInput const&, MatchState& state) const
|
||||
{
|
||||
state.instruction_position += offset();
|
||||
|
|
|
@ -31,6 +31,7 @@ using ByteCodeValueType = u64;
|
|||
__ENUMERATE_OPCODE(ForkReplaceJump) \
|
||||
__ENUMERATE_OPCODE(ForkReplaceStay) \
|
||||
__ENUMERATE_OPCODE(FailForks) \
|
||||
__ENUMERATE_OPCODE(PopSaved) \
|
||||
__ENUMERATE_OPCODE(SaveLeftCaptureGroup) \
|
||||
__ENUMERATE_OPCODE(SaveRightCaptureGroup) \
|
||||
__ENUMERATE_OPCODE(SaveRightNamedCaptureGroup) \
|
||||
|
@ -266,9 +267,15 @@ public:
|
|||
switch (type) {
|
||||
case LookAroundType::LookAhead: {
|
||||
// SAVE
|
||||
// FORKJUMP _BODY
|
||||
// POPSAVED
|
||||
// LABEL _BODY
|
||||
// REGEXP BODY
|
||||
// RESTORE
|
||||
empend((ByteCodeValueType)OpCodeId::Save);
|
||||
empend((ByteCodeValueType)OpCodeId::ForkJump);
|
||||
empend((ByteCodeValueType)1);
|
||||
empend((ByteCodeValueType)OpCodeId::PopSaved);
|
||||
extend(move(lookaround_body));
|
||||
empend((ByteCodeValueType)OpCodeId::Restore);
|
||||
return;
|
||||
|
@ -613,6 +620,14 @@ public:
|
|||
ByteString arguments_string() const override { return ByteString::empty(); }
|
||||
};
|
||||
|
||||
class OpCode_PopSaved final : public OpCode {
|
||||
public:
|
||||
ExecutionResult execute(MatchInput const& input, MatchState& state) const override;
|
||||
ALWAYS_INLINE OpCodeId opcode_id() const override { return OpCodeId::PopSaved; }
|
||||
ALWAYS_INLINE size_t size() const override { return 1; }
|
||||
ByteString arguments_string() const override { return ByteString::empty(); }
|
||||
};
|
||||
|
||||
class OpCode_Save final : public OpCode {
|
||||
public:
|
||||
ExecutionResult execute(MatchInput const& input, MatchState& state) const override;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
|
||||
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2023-2025, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -1558,23 +1558,32 @@ Optional<URL> Parser::basic_parse(StringView raw_input, Optional<URL const&> bas
|
|||
buffer.clear();
|
||||
state = State::Fragment;
|
||||
}
|
||||
// 3. Otherwise:
|
||||
else {
|
||||
// 1. If c is not the EOF code point, not a URL code point, and not U+0025 (%), invalid-URL-unit validation error.
|
||||
if (code_point != end_of_file && !is_url_code_point(code_point) && code_point != '%')
|
||||
// 3. Otherwise, if c is U+0020 SPACE:
|
||||
else if (code_point == ' ') {
|
||||
// 1. If remaining starts with U+003F (?) or U+003F (#), then append "%20" to url’s path.
|
||||
if (auto remaining = get_remaining(); remaining.starts_with('?') || remaining.starts_with('#')) {
|
||||
buffer.append("%20"sv);
|
||||
}
|
||||
// 2. Otherwise, append U+0020 SPACE to url’s path.
|
||||
else {
|
||||
buffer.append(' ');
|
||||
}
|
||||
}
|
||||
// 4. Otherwise, if c is not the EOF code point:
|
||||
else if (code_point != end_of_file) {
|
||||
// 1. If c is not a URL code point and not U+0025 (%), invalid-URL-unit validation error.
|
||||
if (!is_url_code_point(code_point) && code_point != '%')
|
||||
report_validation_error();
|
||||
|
||||
// 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, validation error.
|
||||
// 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error.
|
||||
if (code_point == '%' && !remaining_starts_with_two_ascii_hex_digits())
|
||||
report_validation_error();
|
||||
|
||||
// 3. If c is not the EOF code point, UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path.
|
||||
if (code_point != end_of_file) {
|
||||
append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control);
|
||||
} else {
|
||||
url->m_data->paths[0] = buffer.to_string_without_validation();
|
||||
buffer.clear();
|
||||
}
|
||||
// 3. UTF-8 percent-encode c using the C0 control percent-encode set and append the result to url’s path.
|
||||
append_percent_encoded_if_necessary(buffer, code_point, PercentEncodeSet::C0Control);
|
||||
} else {
|
||||
url->m_data->paths[0] = buffer.to_string_without_validation();
|
||||
buffer.clear();
|
||||
}
|
||||
break;
|
||||
// -> query state, https://url.spec.whatwg.org/#query-state
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue