From 0a2aa8710745c4c89de972cb33d09d9723a8b576 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Mon, 9 Dec 2024 16:31:18 +0900 Subject: [PATCH 001/237] =?UTF-8?q?LibWeb:=20Assign=20ARIA=20role=20?= =?UTF-8?q?=E2=80=9Cswitch=E2=80=9D=20to=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 6 +- .../wpt-import/html-aam/roles.tentative.txt | 9 +++ .../wpt-import/html-aam/roles.tentative.html | 59 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.tentative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles.tentative.html diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index d3696c8cb213a..bec7f08eeb19f 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -2355,8 +2355,12 @@ Optional HTMLInputElement::default_role() const if (type_state() == TypeAttributeState::Button) return ARIA::Role::button; // https://www.w3.org/TR/html-aria/#el-input-checkbox - if (type_state() == TypeAttributeState::Checkbox) + if (type_state() == TypeAttributeState::Checkbox) { + // https://github.com/w3c/html-aam/issues/496 + if (has_attribute("switch"_string)) + return ARIA::Role::switch_; return ARIA::Role::checkbox; + } // https://www.w3.org/TR/html-aria/#el-input-email if (type_state() == TypeAttributeState::Email && !has_attribute(AttributeNames::list)) return ARIA::Role::textbox; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.tentative.txt new file mode 100644 index 0000000000000..85354c236f881 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.tentative.txt @@ -0,0 +1,9 @@ +Harness status: OK + +Found 4 tests + +4 Pass +Pass el-input-checkbox-switch +Pass el-thead +Pass el-tbody +Pass el-tfoot \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.tentative.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.tentative.html new file mode 100644 index 0000000000000..812c72991ead4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.tentative.html @@ -0,0 +1,59 @@ + + + + HTML-AAM Role Verification Tests + + + + + + + + + + +

Tests the computedrole mappings defined in HTML-AAM. Most test names correspond to a unique ID defined in the spec.

+ +

These should remain in alphabetical order, and include all HTML tagnames. If a tag is not tested here, include a pointer to the file where it is tested, such as: <!-- caption -> ./table-roles.html -->

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
abc
123
456
xyz
+ + + + + From 0c24c9150a9f5e73ffd66e01cc519c2177b953fb Mon Sep 17 00:00:00 2001 From: Lucien Fiorini Date: Fri, 6 Dec 2024 02:04:02 +0100 Subject: [PATCH 002/237] LibWeb: Parse the qualified name of XML dom elements correctly --- Libraries/LibWeb/XML/XMLDocumentBuilder.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp b/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp index b8c05f5e6257b..e30fdc30f1f51 100644 --- a/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp +++ b/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp @@ -99,7 +99,23 @@ void XMLDocumentBuilder::element_start(const XML::Name& name, HashMaprealm(), m_namespace, MUST(FlyString::from_deprecated_fly_string(name))); + + if (qualified_name_or_err.is_error()) { + m_has_error = true; + return; + } + + auto qualified_name = qualified_name_or_err.value(); + + auto node_or_err = DOM::create_element(m_document, qualified_name.local_name(), qualified_name.namespace_(), qualified_name.prefix()); + + if (node_or_err.is_error()) { + m_has_error = true; + return; + } + + auto node = node_or_err.value(); // When an XML parser with XML scripting support enabled creates a script element, // it must have its parser document set and its "force async" flag must be unset. From 7feb8eb5bf02705b04ca6343e6c3f677fd277819 Mon Sep 17 00:00:00 2001 From: Lucien Fiorini Date: Fri, 6 Dec 2024 02:14:40 +0100 Subject: [PATCH 003/237] Tests: Import WPT test for firstElementChild namespace in xhtml --- ...ment-firstElementChild-namespace-xhtml.txt | 6 ++++ ...nt-firstElementChild-namespace-xhtml.xhtml | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml diff --git a/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.txt b/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.txt new file mode 100644 index 0000000000000..15b9a8d9d96e5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass firstElementChild with namespaces \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml new file mode 100644 index 0000000000000..d420bfda40dad --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml @@ -0,0 +1,28 @@ + + + +firstElementChild with namespaces + + + + +

Test of firstElementChild with namespaces

+
+ +
+
+

The result of this test is +logged above.

+ + + From ae8033b80f30eade4ac76f07f6aebcae94a796d3 Mon Sep 17 00:00:00 2001 From: Lucien Fiorini Date: Sat, 7 Dec 2024 17:27:29 +0100 Subject: [PATCH 004/237] LibWeb: Rename abbreviated variable name --- Libraries/LibWeb/XML/XMLDocumentBuilder.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp b/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp index e30fdc30f1f51..7ae73a40e4ff4 100644 --- a/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp +++ b/Libraries/LibWeb/XML/XMLDocumentBuilder.cpp @@ -99,23 +99,23 @@ void XMLDocumentBuilder::element_start(const XML::Name& name, HashMaprealm(), m_namespace, MUST(FlyString::from_deprecated_fly_string(name))); + auto qualified_name_or_error = DOM::validate_and_extract(m_document->realm(), m_namespace, MUST(FlyString::from_deprecated_fly_string(name))); - if (qualified_name_or_err.is_error()) { + if (qualified_name_or_error.is_error()) { m_has_error = true; return; } - auto qualified_name = qualified_name_or_err.value(); + auto qualified_name = qualified_name_or_error.value(); - auto node_or_err = DOM::create_element(m_document, qualified_name.local_name(), qualified_name.namespace_(), qualified_name.prefix()); + auto node_or_error = DOM::create_element(m_document, qualified_name.local_name(), qualified_name.namespace_(), qualified_name.prefix()); - if (node_or_err.is_error()) { + if (node_or_error.is_error()) { m_has_error = true; return; } - auto node = node_or_err.value(); + auto node = node_or_error.value(); // When an XML parser with XML scripting support enabled creates a script element, // it must have its parser document set and its "force async" flag must be unset. From 3c2bbd45cfb42c7e22dc7b1eecdaacc4d63c2922 Mon Sep 17 00:00:00 2001 From: Lucien Fiorini Date: Sat, 7 Dec 2024 17:28:46 +0100 Subject: [PATCH 005/237] Tests: Mark test as passing in XMLSerializer-serializeToString.txt --- .../domparsing/XMLSerializer-serializeToString.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/LibWeb/Text/expected/wpt-import/domparsing/XMLSerializer-serializeToString.txt b/Tests/LibWeb/Text/expected/wpt-import/domparsing/XMLSerializer-serializeToString.txt index 121fd1b207cc3..c8c54dde9e572 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/domparsing/XMLSerializer-serializeToString.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/domparsing/XMLSerializer-serializeToString.txt @@ -2,13 +2,13 @@ Harness status: OK Found 24 tests -8 Pass -16 Fail +9 Pass +15 Fail Pass check XMLSerializer.serializeToString method could parsing xmldoc to string Pass check XMLSerializer.serializeToString method could parsing document to string Pass Check if the default namespace is correctly reset. Pass Check if there is no redundant empty namespace declaration. -Fail Check if redundant xmlns="..." is dropped. +Pass Check if redundant xmlns="..." is dropped. Pass Check if inconsistent xmlns="..." is dropped. Fail Check if an attribute with namespace and no prefix is serialized with the nearest-declared prefix Fail Check if an attribute with namespace and no prefix is serialized with the nearest-declared prefix even if the prefix is assigned to another namespace. From e08f6a69b2e7cfcc363098555b5d90a0aab7e73c Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sun, 8 Dec 2024 19:58:50 +0400 Subject: [PATCH 006/237] LibWasm: Respect instance.types() bounds --- Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 142e8859454c2..f920255431893 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -17,7 +17,7 @@ namespace Wasm { Optional Store::allocate(ModuleInstance& instance, Module const& module, CodeSection::Code const& code, TypeIndex type_index) { FunctionAddress address { m_functions.size() }; - if (type_index.value() > instance.types().size()) + if (type_index.value() >= instance.types().size()) return {}; auto& type = instance.types()[type_index.value()]; From 97d99aa8d6b11481076ce33ea1b11df71f6d33f9 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sun, 8 Dec 2024 20:50:31 +0400 Subject: [PATCH 007/237] AK: Verify that HashMap is not empty in take_first This makes the behavior uniform with: - HashTable - SinglyLinkedList - Vector --- AK/HashMap.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AK/HashMap.h b/AK/HashMap.h index c4c72bbcfdc77..c60a9cd143c8e 100644 --- a/AK/HashMap.h +++ b/AK/HashMap.h @@ -246,6 +246,7 @@ class HashMap { V take_first() requires(IsOrdered) { + VERIFY(!is_empty()); return take(begin()->key).release_value(); } From 6f81b801142a2b8a3befc5d17dd2282215571fc2 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sun, 8 Dec 2024 21:26:06 +0400 Subject: [PATCH 008/237] Everywhere: Include HashMap only where it's actually used --- AK/FlyString.cpp | 2 +- Libraries/LibCore/EventReceiver.h | 2 +- Libraries/LibCore/FileWatcher.h | 1 + Libraries/LibGC/ConservativeVector.h | 1 - Libraries/LibJS/AST.cpp | 1 - Libraries/LibJS/AST.h | 1 - Libraries/LibJS/Runtime/MapIterator.h | 1 - Libraries/LibJS/Runtime/MapPrototype.cpp | 1 - Libraries/LibJS/Runtime/Object.h | 1 - Libraries/LibRegex/RegexMatch.h | 1 - Libraries/LibRegex/RegexParser.h | 1 + Libraries/LibUnicode/CurrencyCode.cpp | 1 + Libraries/LibUnicode/CurrencyCode.h | 1 - Libraries/LibWeb/Bindings/Intrinsics.cpp | 1 - Libraries/LibWeb/Crypto/SubtleCrypto.h | 1 - Libraries/LibWeb/HTML/BrowsingContextGroup.h | 1 - Libraries/LibWeb/IndexedDB/Internal/Database.h | 1 - Libraries/LibWeb/Loader/Resource.h | 1 - Libraries/LibWeb/Worker/WebWorkerClient.h | 1 - Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm | 1 + Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp | 1 + Libraries/LibXML/DOM/Node.cpp | 1 - Services/RequestServer/Request.h | 1 - Services/WebContent/WebDriverConnection.h | 1 - Tests/AK/TestJSON.cpp | 1 - UI/Qt/WebContentView.h | 1 - 26 files changed, 7 insertions(+), 21 deletions(-) diff --git a/AK/FlyString.cpp b/AK/FlyString.cpp index 13f08ddfc4ea2..2f644de88471a 100644 --- a/AK/FlyString.cpp +++ b/AK/FlyString.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/Libraries/LibCore/EventReceiver.h b/Libraries/LibCore/EventReceiver.h index 32b44c83e4291..ac95020ac87c0 100644 --- a/Libraries/LibCore/EventReceiver.h +++ b/Libraries/LibCore/EventReceiver.h @@ -10,11 +10,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include diff --git a/Libraries/LibCore/FileWatcher.h b/Libraries/LibCore/FileWatcher.h index ea05ad0b841e3..2df8203989e5e 100644 --- a/Libraries/LibCore/FileWatcher.h +++ b/Libraries/LibCore/FileWatcher.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibGC/ConservativeVector.h b/Libraries/LibGC/ConservativeVector.h index 150525a7cea3a..8277e6bcbcdb9 100644 --- a/Libraries/LibGC/ConservativeVector.h +++ b/Libraries/LibGC/ConservativeVector.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 6764b1ac87260..d08433cc55cb3 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -7,7 +7,6 @@ */ #include -#include #include #include #include diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index dec69d2ec5983..cd3fe0b6c15ad 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -10,7 +10,6 @@ #include #include -#include #include #include #include diff --git a/Libraries/LibJS/Runtime/MapIterator.h b/Libraries/LibJS/Runtime/MapIterator.h index 224dec301fb30..e2d4f1fd31afb 100644 --- a/Libraries/LibJS/Runtime/MapIterator.h +++ b/Libraries/LibJS/Runtime/MapIterator.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include diff --git a/Libraries/LibJS/Runtime/MapPrototype.cpp b/Libraries/LibJS/Runtime/MapPrototype.cpp index 048e891f7fedc..bddd63d01c0ce 100644 --- a/Libraries/LibJS/Runtime/MapPrototype.cpp +++ b/Libraries/LibJS/Runtime/MapPrototype.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 606d86fb9e679..e1032884c6606 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -8,7 +8,6 @@ #pragma once #include -#include #include #include #include diff --git a/Libraries/LibRegex/RegexMatch.h b/Libraries/LibRegex/RegexMatch.h index bdeb3baaaef38..1593647fbe8df 100644 --- a/Libraries/LibRegex/RegexMatch.h +++ b/Libraries/LibRegex/RegexMatch.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/Libraries/LibRegex/RegexParser.h b/Libraries/LibRegex/RegexParser.h index d5634e892461d..6ac48c0053131 100644 --- a/Libraries/LibRegex/RegexParser.h +++ b/Libraries/LibRegex/RegexParser.h @@ -12,6 +12,7 @@ #include "RegexOptions.h" #include +#include #include #include #include diff --git a/Libraries/LibUnicode/CurrencyCode.cpp b/Libraries/LibUnicode/CurrencyCode.cpp index 1457f0594b8ce..e6a1326318e14 100644 --- a/Libraries/LibUnicode/CurrencyCode.cpp +++ b/Libraries/LibUnicode/CurrencyCode.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include namespace Unicode { diff --git a/Libraries/LibUnicode/CurrencyCode.h b/Libraries/LibUnicode/CurrencyCode.h index 11ca5f1c3d9fe..c33d31e8e3685 100644 --- a/Libraries/LibUnicode/CurrencyCode.h +++ b/Libraries/LibUnicode/CurrencyCode.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibWeb/Bindings/Intrinsics.cpp b/Libraries/LibWeb/Bindings/Intrinsics.cpp index e077138ed573b..de2585589d2f9 100644 --- a/Libraries/LibWeb/Bindings/Intrinsics.cpp +++ b/Libraries/LibWeb/Bindings/Intrinsics.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.h b/Libraries/LibWeb/Crypto/SubtleCrypto.h index 6cc934a973d3d..f8ca8e032edaa 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibWeb/HTML/BrowsingContextGroup.h b/Libraries/LibWeb/HTML/BrowsingContextGroup.h index 1669518b03668..583b024e7acf9 100644 --- a/Libraries/LibWeb/HTML/BrowsingContextGroup.h +++ b/Libraries/LibWeb/HTML/BrowsingContextGroup.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibWeb/IndexedDB/Internal/Database.h b/Libraries/LibWeb/IndexedDB/Internal/Database.h index 4b2de0f8a326f..8550eaa59acce 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Database.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Database.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibWeb/Loader/Resource.h b/Libraries/LibWeb/Loader/Resource.h index d06c06d951da7..bd475594c5d1b 100644 --- a/Libraries/LibWeb/Loader/Resource.h +++ b/Libraries/LibWeb/Loader/Resource.h @@ -7,7 +7,6 @@ #pragma once #include -#include #include #include #include diff --git a/Libraries/LibWeb/Worker/WebWorkerClient.h b/Libraries/LibWeb/Worker/WebWorkerClient.h index e1f4df0f3c48a..6e40be7e187ba 100644 --- a/Libraries/LibWeb/Worker/WebWorkerClient.h +++ b/Libraries/LibWeb/Worker/WebWorkerClient.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm b/Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm index 4362367d27325..8652d2c5267b7 100644 --- a/Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm +++ b/Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm @@ -5,6 +5,7 @@ */ #include +#include #include #include #include diff --git a/Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp b/Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp index 15d64919c1897..18a7fb6bd7012 100644 --- a/Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp +++ b/Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include diff --git a/Libraries/LibXML/DOM/Node.cpp b/Libraries/LibXML/DOM/Node.cpp index 0fb50ead62aa7..088088fa3f406 100644 --- a/Libraries/LibXML/DOM/Node.cpp +++ b/Libraries/LibXML/DOM/Node.cpp @@ -4,7 +4,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include diff --git a/Services/RequestServer/Request.h b/Services/RequestServer/Request.h index a166dd608f482..ade90b262625a 100644 --- a/Services/RequestServer/Request.h +++ b/Services/RequestServer/Request.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include diff --git a/Services/WebContent/WebDriverConnection.h b/Services/WebContent/WebDriverConnection.h index a5d7380df4794..c1789c222df3c 100644 --- a/Services/WebContent/WebDriverConnection.h +++ b/Services/WebContent/WebDriverConnection.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include #include diff --git a/Tests/AK/TestJSON.cpp b/Tests/AK/TestJSON.cpp index 3178f2dd64ccf..438f990167259 100644 --- a/Tests/AK/TestJSON.cpp +++ b/Tests/AK/TestJSON.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include diff --git a/UI/Qt/WebContentView.h b/UI/Qt/WebContentView.h index 0d20a368830ba..377c32a0df310 100644 --- a/UI/Qt/WebContentView.h +++ b/UI/Qt/WebContentView.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include From 399af2416aeba95519bb37ef75777e95c0de0555 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sat, 30 Nov 2024 18:23:13 +0400 Subject: [PATCH 009/237] LibWeb: Do not pass cheap-to-copy enums by reference --- Libraries/LibWeb/CSS/ComputedValues.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index ad3453c7c24b7..ef5d7597f604a 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -837,10 +837,10 @@ class MutableComputedValues final : public ComputedValues { void set_grid_row_start(CSS::GridTrackPlacement value) { m_noninherited.grid_row_start = value; } void set_column_count(CSS::ColumnCount value) { m_noninherited.column_count = value; } void set_column_gap(Variant const& column_gap) { m_noninherited.column_gap = column_gap; } - void set_column_span(CSS::ColumnSpan const& column_span) { m_noninherited.column_span = column_span; } + void set_column_span(CSS::ColumnSpan const column_span) { m_noninherited.column_span = column_span; } void set_column_width(CSS::Size const& column_width) { m_noninherited.column_width = column_width; } void set_row_gap(Variant const& row_gap) { m_noninherited.row_gap = row_gap; } - void set_border_collapse(CSS::BorderCollapse const& border_collapse) { m_inherited.border_collapse = border_collapse; } + void set_border_collapse(CSS::BorderCollapse const border_collapse) { m_inherited.border_collapse = border_collapse; } void set_grid_template_areas(Vector> const& grid_template_areas) { m_noninherited.grid_template_areas = grid_template_areas; } void set_grid_auto_flow(CSS::GridAutoFlow grid_auto_flow) { m_noninherited.grid_auto_flow = grid_auto_flow; } void set_transition_delay(CSS::Time const& transition_delay) { m_noninherited.transition_delay = transition_delay; } From 617ad9c3a574c43137d13620e11d61144bf5e44e Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sat, 30 Nov 2024 18:28:56 +0400 Subject: [PATCH 010/237] LibWeb: Move expensive-to-copy arguments --- Libraries/LibWeb/CSS/ComputedValues.h | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index ef5d7597f604a..39030e53a875c 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -721,7 +721,7 @@ class MutableComputedValues final : public ComputedValues { m_inherited = static_cast(other).m_inherited; } - void set_aspect_ratio(AspectRatio aspect_ratio) { m_noninherited.aspect_ratio = aspect_ratio; } + void set_aspect_ratio(AspectRatio aspect_ratio) { m_noninherited.aspect_ratio = move(aspect_ratio); } void set_font_list(NonnullRefPtr font_list) { m_inherited.font_list = move(font_list); } void set_font_size(CSSPixels font_size) { m_inherited.font_size = font_size; } void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; } @@ -745,7 +745,7 @@ class MutableComputedValues final : public ComputedValues { void set_float(CSS::Float value) { m_noninherited.float_ = value; } void set_clear(CSS::Clear value) { m_noninherited.clear = value; } void set_z_index(Optional value) { m_noninherited.z_index = value; } - void set_tab_size(Variant value) { m_inherited.tab_size = value; } + void set_tab_size(Variant value) { m_inherited.tab_size = move(value); } void set_text_align(CSS::TextAlign text_align) { m_inherited.text_align = text_align; } void set_text_justify(CSS::TextJustify text_justify) { m_inherited.text_justify = text_justify; } void set_text_decoration_line(Vector value) { m_noninherited.text_decoration_line = move(value); } @@ -759,7 +759,7 @@ class MutableComputedValues final : public ComputedValues { void set_webkit_text_fill_color(Color value) { m_inherited.webkit_text_fill_color = value; } void set_position(CSS::Positioning position) { m_noninherited.position = position; } void set_white_space(CSS::WhiteSpace value) { m_inherited.white_space = value; } - void set_word_spacing(CSS::LengthOrCalculated value) { m_inherited.word_spacing = value; } + void set_word_spacing(CSS::LengthOrCalculated value) { m_inherited.word_spacing = move(value); } void set_word_break(CSS::WordBreak value) { m_inherited.word_break = value; } void set_letter_spacing(CSS::LengthOrCalculated value) { m_inherited.letter_spacing = value; } void set_width(CSS::Size const& width) { m_noninherited.width = width; } @@ -831,10 +831,10 @@ class MutableComputedValues final : public ComputedValues { void set_grid_auto_rows(CSS::GridTrackSizeList value) { m_noninherited.grid_auto_rows = move(value); } void set_grid_template_columns(CSS::GridTrackSizeList value) { m_noninherited.grid_template_columns = move(value); } void set_grid_template_rows(CSS::GridTrackSizeList value) { m_noninherited.grid_template_rows = move(value); } - void set_grid_column_end(CSS::GridTrackPlacement value) { m_noninherited.grid_column_end = value; } - void set_grid_column_start(CSS::GridTrackPlacement value) { m_noninherited.grid_column_start = value; } - void set_grid_row_end(CSS::GridTrackPlacement value) { m_noninherited.grid_row_end = value; } - void set_grid_row_start(CSS::GridTrackPlacement value) { m_noninherited.grid_row_start = value; } + void set_grid_column_end(CSS::GridTrackPlacement value) { m_noninherited.grid_column_end = move(value); } + void set_grid_column_start(CSS::GridTrackPlacement value) { m_noninherited.grid_column_start = move(value); } + void set_grid_row_end(CSS::GridTrackPlacement value) { m_noninherited.grid_row_end = move(value); } + void set_grid_row_start(CSS::GridTrackPlacement value) { m_noninherited.grid_row_start = move(value); } void set_column_count(CSS::ColumnCount value) { m_noninherited.column_count = value; } void set_column_gap(Variant const& column_gap) { m_noninherited.column_gap = column_gap; } void set_column_span(CSS::ColumnSpan const column_span) { m_noninherited.column_span = column_span; } @@ -852,8 +852,8 @@ class MutableComputedValues final : public ComputedValues { void set_unicode_bidi(CSS::UnicodeBidi value) { m_noninherited.unicode_bidi = value; } void set_writing_mode(CSS::WritingMode value) { m_inherited.writing_mode = value; } - void set_fill(SVGPaint value) { m_inherited.fill = value; } - void set_stroke(SVGPaint value) { m_inherited.stroke = value; } + void set_fill(SVGPaint value) { m_inherited.fill = move(value); } + void set_stroke(SVGPaint value) { m_inherited.stroke = move(value); } void set_fill_rule(CSS::FillRule value) { m_inherited.fill_rule = value; } void set_fill_opacity(float value) { m_inherited.fill_opacity = value; } void set_stroke_dasharray(Vector> value) { m_inherited.stroke_dasharray = move(value); } @@ -862,7 +862,7 @@ class MutableComputedValues final : public ComputedValues { void set_stroke_linejoin(CSS::StrokeLinejoin value) { m_inherited.stroke_linejoin = value; } void set_stroke_miterlimit(NumberOrCalculated value) { m_inherited.stroke_miterlimit = value; } void set_stroke_opacity(float value) { m_inherited.stroke_opacity = value; } - void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; } + void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = move(value); } void set_stop_color(Color value) { m_noninherited.stop_color = value; } void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; } void set_text_anchor(CSS::TextAnchor value) { m_inherited.text_anchor = value; } @@ -873,16 +873,16 @@ class MutableComputedValues final : public ComputedValues { void set_mask(MaskReference value) { m_noninherited.mask = value; } void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = value; } void set_mask_image(CSS::AbstractImageStyleValue const& value) { m_noninherited.mask_image = value; } - void set_clip_path(ClipPathReference value) { m_noninherited.clip_path = value; } + void set_clip_path(ClipPathReference value) { m_noninherited.clip_path = move(value); } void set_clip_rule(CSS::ClipRule value) { m_inherited.clip_rule = value; } - void set_cx(LengthPercentage cx) { m_noninherited.cx = cx; } - void set_cy(LengthPercentage cy) { m_noninherited.cy = cy; } - void set_r(LengthPercentage r) { m_noninherited.r = r; } - void set_rx(LengthPercentage rx) { m_noninherited.rx = rx; } - void set_ry(LengthPercentage ry) { m_noninherited.ry = ry; } - void set_x(LengthPercentage x) { m_noninherited.x = x; } - void set_y(LengthPercentage y) { m_noninherited.y = y; } + void set_cx(LengthPercentage cx) { m_noninherited.cx = move(cx); } + void set_cy(LengthPercentage cy) { m_noninherited.cy = move(cy); } + void set_r(LengthPercentage r) { m_noninherited.r = move(r); } + void set_rx(LengthPercentage rx) { m_noninherited.rx = move(rx); } + void set_ry(LengthPercentage ry) { m_noninherited.ry = move(ry); } + void set_x(LengthPercentage x) { m_noninherited.x = move(x); } + void set_y(LengthPercentage y) { m_noninherited.y = move(y); } void set_math_shift(CSS::MathShift value) { m_inherited.math_shift = value; } void set_math_style(CSS::MathStyle value) { m_inherited.math_style = value; } From 10311fba87e3d2ef7029c10dc72524bcc45ada2d Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Mon, 9 Dec 2024 22:08:51 +0400 Subject: [PATCH 011/237] LibWeb: Align mfrac Padding with Updated MathML Core Spec This aligns with the transition from the MathML Core Working Draft (27 November 2023) to the Editor's Draft (26 November 2024). --- Libraries/LibWeb/MathML/Default.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/LibWeb/MathML/Default.css b/Libraries/LibWeb/MathML/Default.css index 1104b7fb3979a..75a654be5a640 100644 --- a/Libraries/LibWeb/MathML/Default.css +++ b/Libraries/LibWeb/MathML/Default.css @@ -71,8 +71,7 @@ mtd { /* Fractions */ mfrac { - padding-inline-start: 1px; - padding-inline-end: 1px; + padding-inline: 1px; } mfrac > * { math-depth: auto-add; From f0bfb7f69739e2e2a05f58105d000f5a8c080816 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Fri, 6 Dec 2024 16:20:36 -0700 Subject: [PATCH 012/237] LibWeb: Don't capture local variables in HTMLImageElement::decode --- Libraries/LibWeb/HTML/HTMLImageElement.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Libraries/LibWeb/HTML/HTMLImageElement.cpp index 7ef9fbe40c8c0..c24da1e4a7531 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -354,7 +354,7 @@ WebIDL::ExceptionOr> HTMLImageElement::decode() const Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(heap(), [this, promise, &realm, &global] { Platform::EventLoopPlugin::the().spin_until(GC::create_function(heap(), [&] { auto queue_reject_task = [&](String const& message) { - queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, &promise, &message] { + queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise, message = String(message)] { auto exception = WebIDL::EncodingError::create(realm, message); HTML::TemporaryExecutionContext context(realm); WebIDL::reject_promise(realm, promise, exception); @@ -391,7 +391,7 @@ WebIDL::ExceptionOr> HTMLImageElement::decode() const // FIXME: If decoding fails (for example due to invalid image data), then queue a global task on the DOM manipulation task source with global to reject promise with an "EncodingError" DOMException. // NOTE: For now we just resolve it. - queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, &promise] { + queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise] { HTML::TemporaryExecutionContext context(realm); WebIDL::resolve_promise(realm, promise, JS::js_undefined()); })); From 49ff5eb4d8398799b8a3a16dc0fbca8ca00c7c15 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 30 Apr 2024 07:24:37 -0400 Subject: [PATCH 013/237] LibWeb: Do not move heap functions into other heap functions in Fetch In particular, the processBody callback here *can't* move the processBodyError callback. It is needed a few lines after. Passing by value is safe and intended here. --- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index afd2ca7953dff..921194c637b1c 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -568,7 +568,7 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra } // 3. Let processBody given bytes be these steps: - auto process_body = GC::create_function(vm.heap(), [&realm, request, response, &fetch_params, process_body_error = move(process_body_error)](ByteBuffer bytes) { + auto process_body = GC::create_function(vm.heap(), [&realm, request, response, &fetch_params, process_body_error](ByteBuffer bytes) { // 1. If bytes do not match request’s integrity metadata, then run processBodyError and abort these steps. if (!TRY_OR_IGNORE(SRI::do_bytes_match_metadata_list(bytes, request->integrity_metadata()))) { process_body_error->function()({}); @@ -766,7 +766,7 @@ void fetch_response_handover(JS::Realm& realm, Infrastructure::FetchParams const // 3. If internalResponse's body is null, then queue a fetch task to run processBody given null, with // fetchParams’s task destination. if (!internal_response->body()) { - Infrastructure::queue_fetch_task(fetch_params.controller(), task_destination, GC::create_function(vm.heap(), [process_body = move(process_body)]() { + Infrastructure::queue_fetch_task(fetch_params.controller(), task_destination, GC::create_function(vm.heap(), [process_body]() { process_body->function()({}); })); } From 953fe75271645931952b47a6fdd013180eb970a6 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 4 Nov 2024 16:06:01 +0100 Subject: [PATCH 014/237] LibWeb: Remove exception handling from safely extracting response bodies The entire purpose of this AO is to avoid handling exceptions, which we can do now that the underlying AOs do not throw exceptions on OOM. --- Libraries/LibWeb/Fetch/BodyInit.cpp | 4 ++-- Libraries/LibWeb/Fetch/BodyInit.h | 2 +- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 22 +++++++++--------- .../Fetch/Infrastructure/HTTP/Bodies.cpp | 4 ++-- .../LibWeb/Fetch/Infrastructure/HTTP/Bodies.h | 2 +- Libraries/LibWeb/HTML/Navigable.cpp | 23 +++++++++---------- Libraries/LibWeb/HTML/Navigable.h | 4 ++-- Libraries/LibWeb/XHR/XMLHttpRequest.cpp | 6 ++--- 8 files changed, 33 insertions(+), 34 deletions(-) diff --git a/Libraries/LibWeb/Fetch/BodyInit.cpp b/Libraries/LibWeb/Fetch/BodyInit.cpp index 7fc993cdbdc4a..8852be066de4d 100644 --- a/Libraries/LibWeb/Fetch/BodyInit.cpp +++ b/Libraries/LibWeb/Fetch/BodyInit.cpp @@ -23,7 +23,7 @@ namespace Web::Fetch { // https://fetch.spec.whatwg.org/#bodyinit-safely-extract -WebIDL::ExceptionOr safely_extract_body(JS::Realm& realm, BodyInitOrReadableBytes const& object) +Infrastructure::BodyWithType safely_extract_body(JS::Realm& realm, BodyInitOrReadableBytes const& object) { // 1. If object is a ReadableStream object, then: if (auto const* stream = object.get_pointer>()) { @@ -32,7 +32,7 @@ WebIDL::ExceptionOr safely_extract_body(JS::Realm& } // 2. Return the result of extracting object. - return extract_body(realm, object); + return MUST(extract_body(realm, object)); } // https://fetch.spec.whatwg.org/#concept-bodyinit-extract diff --git a/Libraries/LibWeb/Fetch/BodyInit.h b/Libraries/LibWeb/Fetch/BodyInit.h index 2b49913e82150..aaec167b98154 100644 --- a/Libraries/LibWeb/Fetch/BodyInit.h +++ b/Libraries/LibWeb/Fetch/BodyInit.h @@ -17,7 +17,7 @@ namespace Web::Fetch { using BodyInit = Variant, GC::Root, GC::Root, GC::Root, GC::Root, String>; using BodyInitOrReadableBytes = Variant, GC::Root, GC::Root, GC::Root, GC::Root, String, ReadonlyBytes>; -WebIDL::ExceptionOr safely_extract_body(JS::Realm&, BodyInitOrReadableBytes const&); +Infrastructure::BodyWithType safely_extract_body(JS::Realm&, BodyInitOrReadableBytes const&); WebIDL::ExceptionOr extract_body(JS::Realm&, BodyInitOrReadableBytes const&, bool keepalive = false); } diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 921194c637b1c..5fc26010b981c 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -122,7 +122,7 @@ WebIDL::ExceptionOr> fetch(JS::Realm& r // 8. If request’s body is a byte sequence, then set request’s body to request’s body as a body. if (auto const* buffer = request.body().get_pointer()) - request.set_body(TRY(Infrastructure::byte_sequence_as_body(realm, buffer->bytes()))); + request.set_body(Infrastructure::byte_sequence_as_body(realm, buffer->bytes())); // 9. If request’s window is "client", then set request’s window to request’s client, if request’s client’s global // object is a Window object; otherwise "no-window". @@ -576,7 +576,7 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Infra } // 2. Set response’s body to bytes as a body. - response->set_body(TRY_OR_IGNORE(Infrastructure::byte_sequence_as_body(realm, bytes))); + response->set_body(Infrastructure::byte_sequence_as_body(realm, bytes)); // 3. Run fetch response handover given fetchParams and response. fetch_response_handover(realm, fetch_params, *response); @@ -807,7 +807,7 @@ WebIDL::ExceptionOr> scheme_fetch(JS::Realm& realm, Inf auto header = Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html;charset=utf-8"sv); response->header_list()->append(move(header)); - response->set_body(MUST(Infrastructure::byte_sequence_as_body(realm, ""sv.bytes()))); + response->set_body(Infrastructure::byte_sequence_as_body(realm, ""sv.bytes())); return PendingResponse::create(vm, request, response); } @@ -845,13 +845,13 @@ WebIDL::ExceptionOr> scheme_fetch(JS::Realm& realm, Inf // 8. If request’s header list does not contain `Range`: if (!request->header_list()->contains("Range"sv.bytes())) { // 1. Let bodyWithType be the result of safely extracting blob. - auto body_with_type = TRY(safely_extract_body(realm, blob->raw_bytes())); + auto body_with_type = safely_extract_body(realm, blob->raw_bytes()); // 2. Set response’s status message to `OK`. response->set_status_message(MUST(ByteBuffer::copy("OK"sv.bytes()))); // 3. Set response’s body to bodyWithType’s body. - response->set_body(move(body_with_type.body)); + response->set_body(body_with_type.body); // 4. Set response’s header list to « (`Content-Length`, serializedFullLength), (`Content-Type`, type) ». auto content_length_header = Infrastructure::Header::from_string_pair("Content-Length"sv, serialized_full_length); @@ -903,7 +903,7 @@ WebIDL::ExceptionOr> scheme_fetch(JS::Realm& realm, Inf auto sliced_blob = TRY(blob->slice(*range_start, *range_end + 1, type)); // 9. Let slicedBodyWithType be the result of safely extracting slicedBlob. - auto sliced_body_with_type = TRY(safely_extract_body(realm, sliced_blob->raw_bytes())); + auto sliced_body_with_type = safely_extract_body(realm, sliced_blob->raw_bytes()); // 10. Set response’s body to slicedBodyWithType’s body. response->set_body(sliced_body_with_type.body); @@ -958,7 +958,7 @@ WebIDL::ExceptionOr> scheme_fetch(JS::Realm& realm, Inf auto header = Infrastructure::Header::from_string_pair("Content-Type"sv, mime_type); response->header_list()->append(move(header)); - response->set_body(TRY(Infrastructure::byte_sequence_as_body(realm, data_url_struct.value().body))); + response->set_body(Infrastructure::byte_sequence_as_body(realm, data_url_struct.value().body)); return PendingResponse::create(vm, request, response); } // -> "file" @@ -1308,8 +1308,8 @@ WebIDL::ExceptionOr> http_redirect_fetch(JS::Realm& rea auto converted_source = source.has() ? BodyInitOrReadableBytes { source.get() } : BodyInitOrReadableBytes { source.get>() }; - auto [body, _] = TRY(safely_extract_body(realm, converted_source)); - request->set_body(move(body)); + auto [body, _] = safely_extract_body(realm, converted_source); + request->set_body(body); } // 15. Let timingInfo be fetchParams’s timing info. @@ -2107,8 +2107,8 @@ WebIDL::ExceptionOr> http_network_or_cache_fetch(JS::Re auto converted_source = source.has() ? BodyInitOrReadableBytes { source.get() } : BodyInitOrReadableBytes { source.get>() }; - auto [body, _] = TRY_OR_IGNORE(safely_extract_body(realm, converted_source)); - request->set_body(move(body)); + auto [body, _] = safely_extract_body(realm, converted_source); + request->set_body(body); } // 3. If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then: diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp index a07a854b5400f..a07d67271b66b 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp @@ -134,10 +134,10 @@ void Body::incrementally_read_loop(Streams::ReadableStreamDefaultReader& reader, } // https://fetch.spec.whatwg.org/#byte-sequence-as-a-body -WebIDL::ExceptionOr> byte_sequence_as_body(JS::Realm& realm, ReadonlyBytes bytes) +GC::Ref byte_sequence_as_body(JS::Realm& realm, ReadonlyBytes bytes) { // To get a byte sequence bytes as a body, return the body of the result of safely extracting bytes. - auto [body, _] = TRY(safely_extract_body(realm, bytes)); + auto [body, _] = safely_extract_body(realm, bytes); return body; } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h index 6318f9dee5532..c291a81ddb756 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h @@ -76,6 +76,6 @@ struct BodyWithType { Optional type; }; -WebIDL::ExceptionOr> byte_sequence_as_body(JS::Realm&, ReadonlyBytes); +GC::Ref byte_sequence_as_body(JS::Realm&, ReadonlyBytes); } diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index 3dfc0c4153392..1af55cd4ba1b5 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -618,7 +618,8 @@ static GC::Ptr attempt_to_create_a_non_fetch_scheme_document(NonF } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-from-a-srcdoc-resource -static WebIDL::ExceptionOr> create_navigation_params_from_a_srcdoc_resource(GC::Ptr entry, GC::Ptr navigable, TargetSnapshotParams const& target_snapshot_params, Optional navigation_id) + +static GC::Ref create_navigation_params_from_a_srcdoc_resource(GC::Ptr entry, GC::Ptr navigable, TargetSnapshotParams const& target_snapshot_params, Optional navigation_id) { auto& vm = navigable->vm(); VERIFY(navigable->active_window()); @@ -638,7 +639,7 @@ static WebIDL::ExceptionOr> create_navigation_params_f auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv); response->header_list()->append(move(header)); - response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, document_resource.get().bytes()))); + response->set_body(Fetch::Infrastructure::byte_sequence_as_body(realm, document_resource.get().bytes())); // 3. Let responseOrigin be the result of determining the origin given response's URL, targetSnapshotParams's sandboxing flags, and entry's document state's origin. auto response_origin = determine_the_origin(response->url(), target_snapshot_params.sandboxing_flags, entry->document_state()->origin()); @@ -1100,7 +1101,7 @@ WebIDL::ExceptionOr Navigable::populate_session_history_entry_document( // of creating navigation params from a srcdoc resource given entry, navigable, // targetSnapshotParams, navigationId, and navTimingType. if (document_resource.has()) { - navigation_params = TRY(create_navigation_params_from_a_srcdoc_resource(entry, this, target_snapshot_params, navigation_id)); + navigation_params = create_navigation_params_from_a_srcdoc_resource(entry, this, target_snapshot_params, navigation_id); } // 2. Otherwise, if all of the following are true: // - entry's URL's scheme is a fetch scheme; and @@ -1377,7 +1378,7 @@ WebIDL::ExceptionOr Navigable::navigate(NavigateParams params) // 1. Queue a global task on the navigation and traversal task source given navigable's active window to navigate to a javascript: URL given navigable, url, historyHandling, initiatorOriginSnapshot, and cspNavigationType. VERIFY(active_window()); queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), GC::create_function(heap(), [this, url, history_handling, initiator_origin_snapshot, csp_navigation_type, navigation_id] { - (void)navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type, navigation_id); + navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type, navigation_id); })); // 2. Return. @@ -1604,7 +1605,7 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_fragment(URL::URL const& url, // https://html.spec.whatwg.org/multipage/browsing-the-web.html#evaluate-a-javascript:-url // https://whatpr.org/html/9893/browsing-the-web.html#evaluate-a-javascript:-url -WebIDL::ExceptionOr> Navigable::evaluate_javascript_url(URL::URL const& url, URL::Origin const& new_document_origin, String navigation_id) +GC::Ptr Navigable::evaluate_javascript_url(URL::URL const& url, URL::Origin const& new_document_origin, String navigation_id) { auto& vm = this->vm(); VERIFY(active_window()); @@ -1652,7 +1653,7 @@ WebIDL::ExceptionOr> Navigable::evaluate_javascript_url(U auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv); response->header_list()->append(move(header)); - response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes()))); + response->set_body(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes())); // 12. Let policyContainer be targetNavigable's active document's policy container. auto const& policy_container = active_document()->policy_container(); @@ -1708,7 +1709,7 @@ WebIDL::ExceptionOr> Navigable::evaluate_javascript_url(U } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url -WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(URL::URL const& url, HistoryHandlingBehavior history_handling, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id) +void Navigable::navigate_to_a_javascript_url(URL::URL const& url, HistoryHandlingBehavior history_handling, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id) { // 1. Assert: historyHandling is "replace". VERIFY(history_handling == HistoryHandlingBehavior::Replace); @@ -1718,7 +1719,7 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(URL::URL const // 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return. if (!initiator_origin.is_same_origin_domain(active_document()->origin())) - return {}; + return; // FIXME: 4. Let request be a new request whose URL is url. @@ -1726,12 +1727,12 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(URL::URL const (void)csp_navigation_type; // 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable, url, and initiatorOrigin. - auto new_document = TRY(evaluate_javascript_url(url, initiator_origin, navigation_id)); + auto new_document = evaluate_javascript_url(url, initiator_origin, navigation_id); // 7. If newDocument is null, then return. if (!new_document) { // NOTE: In this case, some JavaScript code was executed, but no new Document was created, so we will not perform a navigation. - return {}; + return; } // 8. Assert: initiatorOrigin is newDocument's origin. @@ -1776,8 +1777,6 @@ WebIDL::ExceptionOr Navigable::navigate_to_a_javascript_url(URL::URL const traversable_navigable()->append_session_history_traversal_steps(GC::create_function(heap(), [this, history_entry, history_handling, navigation_id] { finalize_a_cross_document_navigation(*this, history_handling, history_entry); })); - - return {}; } // https://html.spec.whatwg.org/multipage/browsing-the-web.html#reload diff --git a/Libraries/LibWeb/HTML/Navigable.h b/Libraries/LibWeb/HTML/Navigable.h index 3d81aa48b529e..a5607f72b25de 100644 --- a/Libraries/LibWeb/HTML/Navigable.h +++ b/Libraries/LibWeb/HTML/Navigable.h @@ -156,8 +156,8 @@ class Navigable : public JS::Cell { WebIDL::ExceptionOr navigate_to_a_fragment(URL::URL const&, HistoryHandlingBehavior, UserNavigationInvolvement, Optional navigation_api_state, String navigation_id); - WebIDL::ExceptionOr> evaluate_javascript_url(URL::URL const&, URL::Origin const& new_document_origin, String navigation_id); - WebIDL::ExceptionOr navigate_to_a_javascript_url(URL::URL const&, HistoryHandlingBehavior, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id); + GC::Ptr evaluate_javascript_url(URL::URL const&, URL::Origin const& new_document_origin, String navigation_id); + void navigate_to_a_javascript_url(URL::URL const&, HistoryHandlingBehavior, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id); bool allowed_by_sandboxing_to_navigate(Navigable const& target, SourceSnapshotParams const&); diff --git a/Libraries/LibWeb/XHR/XMLHttpRequest.cpp b/Libraries/LibWeb/XHR/XMLHttpRequest.cpp index 026b6a6c9a8c6..2423c890788b6 100644 --- a/Libraries/LibWeb/XHR/XMLHttpRequest.cpp +++ b/Libraries/LibWeb/XHR/XMLHttpRequest.cpp @@ -574,15 +574,15 @@ WebIDL::ExceptionOr XMLHttpRequest::send(Optionalhas>()) { auto string_serialized_document = TRY(body->get>().cell()->serialize_fragment(DOMParsing::RequireWellFormed::No)); - m_request_body = TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, string_serialized_document.bytes())); + m_request_body = Fetch::Infrastructure::byte_sequence_as_body(realm, string_serialized_document.bytes()); } // 3. Otherwise: else { // 1. Let bodyWithType be the result of safely extracting body. - auto body_with_type = TRY(Fetch::safely_extract_body(realm, body->downcast())); + auto body_with_type = Fetch::safely_extract_body(realm, body->downcast()); // 2. Set this’s request body to bodyWithType’s body. - m_request_body = move(body_with_type.body); + m_request_body = body_with_type.body; // 3. Set extractedContentType to bodyWithType’s type. extracted_content_type = move(body_with_type.type); From 5cd6d403cae80f8316000db624bfb3c55033cf5d Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 4 Nov 2024 16:06:51 +0100 Subject: [PATCH 015/237] LibWeb: Remove outdated comment about synchronous body initialization We now do this step asynchronously. --- Libraries/LibWeb/Fetch/BodyInit.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Libraries/LibWeb/Fetch/BodyInit.cpp b/Libraries/LibWeb/Fetch/BodyInit.cpp index 8852be066de4d..b5e6e43af02b8 100644 --- a/Libraries/LibWeb/Fetch/BodyInit.cpp +++ b/Libraries/LibWeb/Fetch/BodyInit.cpp @@ -135,7 +135,6 @@ WebIDL::ExceptionOr extract_body(JS::Realm& realm, })); // 11. If source is a byte sequence, then set action to a step that returns source and length to source’s length. - // For now, do it synchronously. if (source.has()) { action = [source = MUST(ByteBuffer::copy(source.get()))]() mutable { return move(source); From 383d303b79055e5450fb6f7725cc8f999d70ccb5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 4 Nov 2024 16:08:37 +0100 Subject: [PATCH 016/237] LibWeb: Enable callbacks in execution contexts when teeing streams This will be needed once fetched response bodies are read using streams. --- Libraries/LibWeb/Streams/AbstractOperations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index d55111d261f3e..3ebc1837478cd 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -709,7 +709,7 @@ class ByteStreamTeeDefaultReadRequest final : public ReadRequest { { // 1. Queue a microtask to perform the following steps: HTML::queue_a_microtask(nullptr, GC::create_function(m_realm->heap(), [this, chunk]() mutable { - HTML::TemporaryExecutionContext execution_context { m_realm }; + HTML::TemporaryExecutionContext execution_context { m_realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; auto controller1 = m_params->branch1->controller()->get>(); auto controller2 = m_params->branch2->controller()->get>(); From 4b4b12165e9bb0bfb475078498f9f612c6ae2c03 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 4 Nov 2024 16:10:32 +0100 Subject: [PATCH 017/237] LibWeb: Release acquired readers after piping through a stream This very partially implements the spec's "finalize" steps for piping streams. --- Libraries/LibWeb/Streams/AbstractOperations.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 3ebc1837478cd..6707c8595623f 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -315,16 +315,18 @@ GC::Ref readable_stream_pipe_to(ReadableStream& source, Writabl WebIDL::resolve_promise(realm, promise, JS::js_undefined()); }); - auto success_steps = GC::create_function(realm.heap(), [promise, &realm, writer](ByteBuffer) { + auto success_steps = GC::create_function(realm.heap(), [promise, &realm, reader, writer](ByteBuffer) { // Make sure we close the acquired writer. WebIDL::resolve_promise(realm, writable_stream_default_writer_close(*writer), JS::js_undefined()); + readable_stream_default_reader_release(*reader); WebIDL::resolve_promise(realm, promise, JS::js_undefined()); }); - auto failure_steps = GC::create_function(realm.heap(), [promise, &realm, writer](JS::Value error) { + auto failure_steps = GC::create_function(realm.heap(), [promise, &realm, reader, writer](JS::Value error) { // Make sure we close the acquired writer. WebIDL::resolve_promise(realm, writable_stream_default_writer_close(*writer), JS::js_undefined()); + readable_stream_default_reader_release(*reader); WebIDL::reject_promise(realm, promise, error); }); From 9396a643b8b78c7dc7ecad16583f25f2417257f0 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 8 Dec 2024 22:18:33 -0500 Subject: [PATCH 018/237] LibWeb: Ensure FilteredResponse setters forward to the base class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec for filtered responses states: Unless stated otherwise a filtered response’s associated concepts (such as its body) refer to the associated concepts of its internal response. This includes setting its associated concepts. In particular, when the filtered response's body is set upon fetching a request with integrity metadata, we must set the internal response's body instead. Further restrictions that apply to filtered response subclasses (such as opaque filtered responses having a status code of 0) are already implemented. --- .../Fetch/Infrastructure/HTTP/Responses.cpp | 2 - .../Fetch/Infrastructure/HTTP/Responses.h | 62 ++++++++++++------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp index 5b39a61cf6714..d1a5e707d9c6e 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.cpp @@ -422,7 +422,6 @@ void OpaqueFilteredResponse::visit_edges(JS::Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_header_list); - visitor.visit(m_body); } GC::Ref OpaqueRedirectFilteredResponse::create(JS::VM& vm, GC::Ref internal_response) @@ -442,7 +441,6 @@ void OpaqueRedirectFilteredResponse::visit_edges(JS::Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_header_list); - visitor.visit(m_body); } } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h index 0eedb9eb62899..2148a79839ce9 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h @@ -67,42 +67,41 @@ class Response : public JS::Cell { void set_type(Type type) { m_type = type; } [[nodiscard]] virtual bool aborted() const { return m_aborted; } - void set_aborted(bool aborted) { m_aborted = aborted; } + virtual void set_aborted(bool aborted) { m_aborted = aborted; } [[nodiscard]] virtual Vector const& url_list() const { return m_url_list; } [[nodiscard]] virtual Vector& url_list() { return m_url_list; } - void set_url_list(Vector url_list) { m_url_list = move(url_list); } + virtual void set_url_list(Vector url_list) { m_url_list = move(url_list); } [[nodiscard]] virtual Status status() const { return m_status; } - void set_status(Status status) { m_status = status; } + virtual void set_status(Status status) { m_status = status; } [[nodiscard]] virtual ReadonlyBytes status_message() const { return m_status_message; } - void set_status_message(ByteBuffer status_message) { m_status_message = move(status_message); } + virtual void set_status_message(ByteBuffer status_message) { m_status_message = move(status_message); } [[nodiscard]] virtual GC::Ref header_list() const { return m_header_list; } - void set_header_list(GC::Ref header_list) { m_header_list = header_list; } + virtual void set_header_list(GC::Ref header_list) { m_header_list = header_list; } - [[nodiscard]] virtual GC::Ptr const& body() const { return m_body; } - [[nodiscard]] virtual GC::Ptr& body() { return m_body; } - void set_body(GC::Ptr body) { m_body = move(body); } + [[nodiscard]] virtual GC::Ptr body() const { return m_body; } + virtual void set_body(GC::Ptr body) { m_body = body; } [[nodiscard]] virtual Optional const& cache_state() const { return m_cache_state; } - void set_cache_state(Optional cache_state) { m_cache_state = move(cache_state); } + virtual void set_cache_state(Optional cache_state) { m_cache_state = move(cache_state); } [[nodiscard]] virtual Vector const& cors_exposed_header_name_list() const { return m_cors_exposed_header_name_list; } - void set_cors_exposed_header_name_list(Vector cors_exposed_header_name_list) { m_cors_exposed_header_name_list = move(cors_exposed_header_name_list); } + virtual void set_cors_exposed_header_name_list(Vector cors_exposed_header_name_list) { m_cors_exposed_header_name_list = move(cors_exposed_header_name_list); } [[nodiscard]] virtual bool range_requested() const { return m_range_requested; } - void set_range_requested(bool range_requested) { m_range_requested = range_requested; } + virtual void set_range_requested(bool range_requested) { m_range_requested = range_requested; } [[nodiscard]] virtual bool request_includes_credentials() const { return m_request_includes_credentials; } - void set_request_includes_credentials(bool request_includes_credentials) { m_request_includes_credentials = request_includes_credentials; } + virtual void set_request_includes_credentials(bool request_includes_credentials) { m_request_includes_credentials = request_includes_credentials; } [[nodiscard]] virtual bool timing_allow_passed() const { return m_timing_allow_passed; } - void set_timing_allow_passed(bool timing_allow_passed) { m_timing_allow_passed = timing_allow_passed; } + virtual void set_timing_allow_passed(bool timing_allow_passed) { m_timing_allow_passed = timing_allow_passed; } [[nodiscard]] virtual BodyInfo const& body_info() const { return m_body_info; } - void set_body_info(BodyInfo body_info) { m_body_info = body_info; } + virtual void set_body_info(BodyInfo body_info) { m_body_info = body_info; } [[nodiscard]] bool has_cross_origin_redirects() const { return m_has_cross_origin_redirects; } void set_has_cross_origin_redirects(bool has_cross_origin_redirects) { m_has_cross_origin_redirects = has_cross_origin_redirects; } @@ -216,20 +215,43 @@ class FilteredResponse : public Response { virtual ~FilteredResponse() = 0; [[nodiscard]] virtual Type type() const override { return m_internal_response->type(); } + [[nodiscard]] virtual bool aborted() const override { return m_internal_response->aborted(); } + virtual void set_aborted(bool aborted) override { m_internal_response->set_aborted(aborted); } + [[nodiscard]] virtual Vector const& url_list() const override { return m_internal_response->url_list(); } [[nodiscard]] virtual Vector& url_list() override { return m_internal_response->url_list(); } + virtual void set_url_list(Vector url_list) override { m_internal_response->set_url_list(move(url_list)); } + [[nodiscard]] virtual Status status() const override { return m_internal_response->status(); } + virtual void set_status(Status status) override { m_internal_response->set_status(status); } + [[nodiscard]] virtual ReadonlyBytes status_message() const override { return m_internal_response->status_message(); } + virtual void set_status_message(ByteBuffer status_message) override { m_internal_response->set_status_message(move(status_message)); } + [[nodiscard]] virtual GC::Ref header_list() const override { return m_internal_response->header_list(); } - [[nodiscard]] virtual GC::Ptr const& body() const override { return m_internal_response->body(); } - [[nodiscard]] virtual GC::Ptr& body() override { return m_internal_response->body(); } + virtual void set_header_list(GC::Ref header_list) override { m_internal_response->set_header_list(header_list); } + + [[nodiscard]] virtual GC::Ptr body() const override { return m_internal_response->body(); } + virtual void set_body(GC::Ptr body) override { m_internal_response->set_body(body); } + [[nodiscard]] virtual Optional const& cache_state() const override { return m_internal_response->cache_state(); } + virtual void set_cache_state(Optional cache_state) override { m_internal_response->set_cache_state(move(cache_state)); } + [[nodiscard]] virtual Vector const& cors_exposed_header_name_list() const override { return m_internal_response->cors_exposed_header_name_list(); } + virtual void set_cors_exposed_header_name_list(Vector cors_exposed_header_name_list) override { m_internal_response->set_cors_exposed_header_name_list(move(cors_exposed_header_name_list)); } + [[nodiscard]] virtual bool range_requested() const override { return m_internal_response->range_requested(); } + virtual void set_range_requested(bool range_requested) override { m_internal_response->set_range_requested(range_requested); } + [[nodiscard]] virtual bool request_includes_credentials() const override { return m_internal_response->request_includes_credentials(); } + virtual void set_request_includes_credentials(bool request_includes_credentials) override { m_internal_response->set_request_includes_credentials(request_includes_credentials); } + [[nodiscard]] virtual bool timing_allow_passed() const override { return m_internal_response->timing_allow_passed(); } + virtual void set_timing_allow_passed(bool timing_allow_passed) override { m_internal_response->set_timing_allow_passed(timing_allow_passed); } + [[nodiscard]] virtual BodyInfo const& body_info() const override { return m_internal_response->body_info(); } + virtual void set_body_info(BodyInfo body_info) override { m_internal_response->set_body_info(move(body_info)); } [[nodiscard]] GC::Ref internal_response() const { return m_internal_response; } @@ -293,8 +315,7 @@ class OpaqueFilteredResponse final : public FilteredResponse { [[nodiscard]] virtual Status status() const override { return 0; } [[nodiscard]] virtual ReadonlyBytes status_message() const override { return {}; } [[nodiscard]] virtual GC::Ref header_list() const override { return m_header_list; } - [[nodiscard]] virtual GC::Ptr const& body() const override { return m_body; } - [[nodiscard]] virtual GC::Ptr& body() override { return m_body; } + [[nodiscard]] virtual GC::Ptr body() const override { return nullptr; } private: OpaqueFilteredResponse(GC::Ref, GC::Ref); @@ -303,7 +324,6 @@ class OpaqueFilteredResponse final : public FilteredResponse { Vector m_url_list; GC::Ref m_header_list; - GC::Ptr m_body; }; // https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect @@ -318,8 +338,7 @@ class OpaqueRedirectFilteredResponse final : public FilteredResponse { [[nodiscard]] virtual Status status() const override { return 0; } [[nodiscard]] virtual ReadonlyBytes status_message() const override { return {}; } [[nodiscard]] virtual GC::Ref header_list() const override { return m_header_list; } - [[nodiscard]] virtual GC::Ptr const& body() const override { return m_body; } - [[nodiscard]] virtual GC::Ptr& body() override { return m_body; } + [[nodiscard]] virtual GC::Ptr body() const override { return nullptr; } private: OpaqueRedirectFilteredResponse(GC::Ref, GC::Ref); @@ -327,6 +346,5 @@ class OpaqueRedirectFilteredResponse final : public FilteredResponse { virtual void visit_edges(JS::Cell::Visitor&) override; GC::Ref m_header_list; - GC::Ptr m_body; }; } From 17d5dfe5974da1e5a7c962a0b6db990c9c806409 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Mon, 4 Nov 2024 14:11:07 +0100 Subject: [PATCH 019/237] LibWeb: Implement Web::Fetch::Body::fully_read() closer to spec By actually using streams, they get marked as disturbed and the `.bodyUsed` API starts to work. Fixes at least 94 subtests in the WPT `fetch/api/request` test suite. Co-authored-by: Timothy Flynn --- .../Fetch/Infrastructure/HTTP/Bodies.cpp | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp index a07d67271b66b..79cce32095b93 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp @@ -1,10 +1,12 @@ /* * Copyright (c) 2022-2023, Linus Groh + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include #include #include #include @@ -12,7 +14,6 @@ #include #include #include -#include namespace Web::Fetch::Infrastructure { @@ -65,43 +66,40 @@ GC::Ref Body::clone(JS::Realm& realm) // https://fetch.spec.whatwg.org/#body-fully-read void Body::fully_read(JS::Realm& realm, Web::Fetch::Infrastructure::Body::ProcessBodyCallback process_body, Web::Fetch::Infrastructure::Body::ProcessBodyErrorCallback process_body_error, TaskDestination task_destination) const { + HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + // FIXME: 1. If taskDestination is null, then set taskDestination to the result of starting a new parallel queue. // FIXME: Handle 'parallel queue' task destination VERIFY(!task_destination.has()); auto task_destination_object = task_destination.get>(); // 2. Let successSteps given a byte sequence bytes be to queue a fetch task to run processBody given bytes, with taskDestination. - auto success_steps = [&realm, process_body, task_destination_object = task_destination_object](ReadonlyBytes bytes) -> ErrorOr { - // Make a copy of the bytes, as the source of the bytes may disappear between the time the task is queued and executed. - auto bytes_copy = TRY(ByteBuffer::copy(bytes)); - queue_fetch_task(*task_destination_object, GC::create_function(realm.heap(), [process_body, bytes_copy = move(bytes_copy)]() mutable { - process_body->function()(move(bytes_copy)); + auto success_steps = [&realm, process_body, task_destination_object](ByteBuffer bytes) { + queue_fetch_task(*task_destination_object, GC::create_function(realm.heap(), [process_body, bytes = move(bytes)]() mutable { + process_body->function()(move(bytes)); })); - return {}; }; - // 3. Let errorSteps optionally given an exception exception be to queue a fetch task to run processBodyError given exception, with taskDestination. - auto error_steps = [&realm, process_body_error, task_destination_object](GC::Ptr exception) { + // 3. Let errorSteps optionally given an exception exception be to queue a fetch task to run processBodyError given + // exception, with taskDestination. + auto error_steps = [&realm, process_body_error, task_destination_object](JS::Value exception) { queue_fetch_task(*task_destination_object, GC::create_function(realm.heap(), [process_body_error, exception]() { process_body_error->function()(exception); })); }; - // 4. Let reader be the result of getting a reader for body’s stream. If that threw an exception, then run errorSteps with that exception and return. + // 4. Let reader be the result of getting a reader for body’s stream. If that threw an exception, then run errorSteps + // with that exception and return. + auto reader = Streams::acquire_readable_stream_default_reader(m_stream); + + if (reader.is_exception()) { + auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), reader.release_error()); + error_steps(throw_completion.release_value().value()); + return; + } + // 5. Read all bytes from reader, given successSteps and errorSteps. - // FIXME: Use streams for these steps. - m_source.visit( - [&](ByteBuffer const& byte_buffer) { - if (auto result = success_steps(byte_buffer); result.is_error()) - error_steps(WebIDL::UnknownError::create(realm, "Out-of-memory"_string)); - }, - [&](GC::Root const& blob) { - if (auto result = success_steps(blob->raw_bytes()); result.is_error()) - error_steps(WebIDL::UnknownError::create(realm, "Out-of-memory"_string)); - }, - [&](Empty) { - error_steps(WebIDL::DOMException::create(realm, "DOMException"_fly_string, "Reading from Blob, FormData or null source is not yet implemented"_string)); - }); + reader.value()->read_all_bytes(GC::create_function(realm.heap(), move(success_steps)), GC::create_function(realm.heap(), move(error_steps))); } // https://fetch.spec.whatwg.org/#body-incrementally-read @@ -123,7 +121,6 @@ void Body::incrementally_read(ProcessBodyChunkCallback process_body_chunk, Proce // https://fetch.spec.whatwg.org/#incrementally-read-loop void Body::incrementally_read_loop(Streams::ReadableStreamDefaultReader& reader, GC::Ref task_destination, ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error) - { auto& realm = reader.realm(); // 1. Let readRequest be the following read request: From 1514197e368afb5b472aef715931f31165cbc9c7 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Mon, 4 Nov 2024 14:37:27 +0100 Subject: [PATCH 020/237] LibWeb: Remove `dom_` from `dom_exception_to_throw_completion` We're not converting `WebIDL::DOMException`, but `WebIDL::Exception` instead. --- Libraries/LibWeb/Bindings/ExceptionOrUtils.h | 4 +-- Libraries/LibWeb/Bindings/MainThreadVM.cpp | 4 +-- Libraries/LibWeb/CSS/FontFaceSet.cpp | 2 +- .../LibWeb/Compression/CompressionStream.cpp | 4 +-- .../Compression/DecompressionStream.cpp | 4 +-- Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 26 +++++++++---------- Libraries/LibWeb/Fetch/FetchMethod.cpp | 2 +- .../Fetch/Fetching/FetchedDataReceiver.cpp | 2 +- .../Fetch/Infrastructure/HTTP/Bodies.cpp | 2 +- Libraries/LibWeb/HTML/Parser/HTMLParser.cpp | 2 +- .../HTML/Scripting/ImportMapParseResult.cpp | 2 +- .../LibWeb/Streams/AbstractOperations.cpp | 12 ++++----- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 2 +- Libraries/LibWeb/WebIDL/Promise.cpp | 2 +- 14 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Libraries/LibWeb/Bindings/ExceptionOrUtils.h b/Libraries/LibWeb/Bindings/ExceptionOrUtils.h index d3561b3d65465..07c3928faa53a 100644 --- a/Libraries/LibWeb/Bindings/ExceptionOrUtils.h +++ b/Libraries/LibWeb/Bindings/ExceptionOrUtils.h @@ -58,7 +58,7 @@ struct ExtractExceptionOrValueType> { } -ALWAYS_INLINE JS::Completion dom_exception_to_throw_completion(JS::VM& vm, auto&& exception) +ALWAYS_INLINE JS::Completion exception_to_throw_completion(JS::VM& vm, auto&& exception) { return exception.visit( [&](WebIDL::SimpleException const& exception) { @@ -97,7 +97,7 @@ JS::ThrowCompletionOr throw_dom_exception_if_needed(JS::VM& vm, F&& fn) auto&& result = fn(); if (result.is_exception()) - return dom_exception_to_throw_completion(vm, result.exception()); + return exception_to_throw_completion(vm, result.exception()); if constexpr (requires(T v) { v.value(); }) return result.value(); diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Libraries/LibWeb/Bindings/MainThreadVM.cpp index 0270ceba9a20b..b51e961fa6a77 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -499,7 +499,7 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // 3. If the previous step threw an exception, then: if (maybe_exception.is_exception()) { // 1. Let completion be Completion Record { [[Type]]: throw, [[Value]]: resolutionError, [[Target]]: empty }. - auto completion = dom_exception_to_throw_completion(main_thread_vm(), maybe_exception.exception()); + auto completion = exception_to_throw_completion(main_thread_vm(), maybe_exception.exception()); // 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion). JS::finish_loading_imported_module(referrer, module_request, payload, completion); @@ -542,7 +542,7 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // 10. If the previous step threw an exception, then: if (url.is_exception()) { // 1. Let completion be Completion Record { [[Type]]: throw, [[Value]]: resolutionError, [[Target]]: empty }. - auto completion = dom_exception_to_throw_completion(main_thread_vm(), url.exception()); + auto completion = exception_to_throw_completion(main_thread_vm(), url.exception()); // 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion). HTML::TemporaryExecutionContext context { *module_map_realm }; diff --git a/Libraries/LibWeb/CSS/FontFaceSet.cpp b/Libraries/LibWeb/CSS/FontFaceSet.cpp index c1a7c1796b681..f06e277a1526c 100644 --- a/Libraries/LibWeb/CSS/FontFaceSet.cpp +++ b/Libraries/LibWeb/CSS/FontFaceSet.cpp @@ -241,7 +241,7 @@ JS::ThrowCompletionOr> FontFaceSet::load(String const& auto result = find_matching_font_faces(realm, font_face_set, font, text); if (result.is_error()) { HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index 80419f01fed9c..b72bbd8c99763 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -55,7 +55,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp auto& vm = realm.vm(); if (auto result = stream->compress_and_enqueue_chunk(chunk); result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, result.exception()); return WebIDL::create_rejected_promise(realm, *throw_completion.release_value()); } @@ -68,7 +68,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp auto& vm = realm.vm(); if (auto result = stream->compress_flush_and_enqueue(); result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, result.exception()); return WebIDL::create_rejected_promise(realm, *throw_completion.release_value()); } diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index a48b7bf3750a5..1b2b909a8a3f0 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -56,7 +56,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct auto& vm = realm.vm(); if (auto result = stream->decompress_and_enqueue_chunk(chunk); result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, result.exception()); return WebIDL::create_rejected_promise(realm, *throw_completion.release_value()); } @@ -69,7 +69,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct auto& vm = realm.vm(); if (auto result = stream->decompress_flush_and_enqueue(); result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, result.exception()); return WebIDL::create_rejected_promise(realm, *throw_completion.release_value()); } diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index bead625845393..e032ae579cf88 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -176,7 +176,7 @@ GC::Ref SubtleCrypto::encrypt(AlgorithmIdentifier const& algori // 10. Let ciphertext be the result of performing the encrypt operation specified by normalizedAlgorithm using algorithm and key and with data as plaintext. auto cipher_text = normalized_algorithm.methods->encrypt(*normalized_algorithm.parameter, key, data); if (cipher_text.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), cipher_text.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), cipher_text.release_error()).release_value().value()); return; } @@ -233,7 +233,7 @@ GC::Ref SubtleCrypto::decrypt(AlgorithmIdentifier const& algori // 10. Let plaintext be the result of performing the decrypt operation specified by normalizedAlgorithm using algorithm and key and with data as ciphertext. auto plain_text = normalized_algorithm.methods->decrypt(*normalized_algorithm.parameter, key, data); if (plain_text.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), plain_text.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), plain_text.release_error()).release_value().value()); return; } @@ -281,7 +281,7 @@ GC::Ref SubtleCrypto::digest(AlgorithmIdentifier const& algorit auto result = algorithm_object.methods->digest(*algorithm_object.parameter, data_buffer); if (result.is_exception()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } @@ -322,7 +322,7 @@ JS::ThrowCompletionOr> SubtleCrypto::generate_key(Algor auto result_or_error = normalized_algorithm.methods->generate_key(*normalized_algorithm.parameter, extractable, key_usages); if (result_or_error.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); return; } auto result = result_or_error.release_value(); @@ -403,7 +403,7 @@ JS::ThrowCompletionOr> SubtleCrypto::import_key(Binding // specified by normalizedAlgorithm using keyData, algorithm, format, extractable and usages. auto maybe_result = normalized_algorithm.methods->import_key(*normalized_algorithm.parameter, format, real_key_data.downcast(), extractable, key_usages); if (maybe_result.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), maybe_result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), maybe_result.release_error()).release_value().value()); return; } auto result = maybe_result.release_value(); @@ -449,7 +449,7 @@ JS::ThrowCompletionOr> SubtleCrypto::export_key(Binding // FIXME: Stash the AlgorithmMethods on the KeyAlgorithm auto normalized_algorithm_or_error = normalize_an_algorithm(realm, algorithm.name(), "exportKey"_string); if (normalized_algorithm_or_error.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), normalized_algorithm_or_error.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), normalized_algorithm_or_error.release_error()).release_value().value()); return; } auto normalized_algorithm = normalized_algorithm_or_error.release_value(); @@ -463,7 +463,7 @@ JS::ThrowCompletionOr> SubtleCrypto::export_key(Binding // 7. Let result be the result of performing the export key operation specified by the [[algorithm]] internal slot of key using key and format. auto result_or_error = normalized_algorithm.methods->export_key(format, key); if (result_or_error.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); return; } @@ -520,7 +520,7 @@ JS::ThrowCompletionOr> SubtleCrypto::sign(AlgorithmIden // 10. Let result be the result of performing the sign operation specified by normalizedAlgorithm using key and algorithm and with data as message. auto result = normalized_algorithm.methods->sign(*normalized_algorithm.parameter, key, data); if (result.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } @@ -584,7 +584,7 @@ JS::ThrowCompletionOr> SubtleCrypto::verify(AlgorithmId // 11. Let result be the result of performing the verify operation specified by normalizedAlgorithm using key, algorithm and signature and with data as message. auto result = normalized_algorithm.methods->verify(*normalized_algorithm.parameter, key, signature, data); if (result.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } @@ -631,7 +631,7 @@ JS::ThrowCompletionOr> SubtleCrypto::derive_bits(Algori // 9. Let result be the result of creating an ArrayBuffer containing the result of performing the derive bits operation specified by normalizedAlgorithm using baseKey, algorithm and length. auto result = normalized_algorithm.methods->derive_bits(*normalized_algorithm.parameter, base_key, length); if (result.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value()); return; } @@ -693,7 +693,7 @@ JS::ThrowCompletionOr> SubtleCrypto::derive_key(Algorit // 13. Let length be the result of performing the get key length algorithm specified by normalizedDerivedKeyAlgorithmLength using derivedKeyType. auto length_result = normalized_derived_key_algorithm_length.methods->get_key_length(*normalized_derived_key_algorithm_length.parameter); if (length_result.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), length_result.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), length_result.release_error()).release_value().value()); return; } @@ -712,14 +712,14 @@ JS::ThrowCompletionOr> SubtleCrypto::derive_key(Algorit // 14. Let secret be the result of performing the derive bits operation specified by normalizedAlgorithm using key, algorithm and length. auto secret = normalized_algorithm.methods->derive_bits(*normalized_algorithm.parameter, base_key, length); if (secret.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), secret.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), secret.release_error()).release_value().value()); return; } // 15. Let result be the result of performing the import key operation specified by normalizedDerivedKeyAlgorithmImport using "raw" as format, secret as keyData, derivedKeyType as algorithm and using extractable and usages. auto result_or_error = normalized_derived_key_algorithm_import.methods->import_key(*normalized_derived_key_algorithm_import.parameter, Bindings::KeyFormat::Raw, secret.release_value()->buffer(), extractable, key_usages); if (result_or_error.is_error()) { - WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); return; } auto result = result_or_error.release_value(); diff --git a/Libraries/LibWeb/Fetch/FetchMethod.cpp b/Libraries/LibWeb/Fetch/FetchMethod.cpp index dea073940ce2f..2d528872543b2 100644 --- a/Libraries/LibWeb/Fetch/FetchMethod.cpp +++ b/Libraries/LibWeb/Fetch/FetchMethod.cpp @@ -36,7 +36,7 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit // as arguments. If this throws an exception, reject p with it and return p. auto exception_or_request_object = Request::construct_impl(realm, input, init); if (exception_or_request_object.is_exception()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, exception_or_request_object.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, exception_or_request_object.exception()); WebIDL::reject_promise(realm, promise_capability, *throw_completion.value()); return promise_capability; } diff --git a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp index 9521b9c5beadb..b03d5ff37b27c 100644 --- a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp @@ -69,7 +69,7 @@ void FetchedDataReceiver::on_data_received(ReadonlyBytes bytes) // 1. Pull from bytes buffer into stream. if (auto result = Streams::readable_stream_pull_from_bytes(m_stream, move(bytes)); result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(m_stream->vm(), result.release_error()); + auto throw_completion = Bindings::exception_to_throw_completion(m_stream->vm(), result.release_error()); dbgln("FetchedDataReceiver: Stream error pulling bytes"); HTML::report_exception(throw_completion, m_stream->realm()); diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp index 79cce32095b93..d2421223191a1 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp @@ -93,7 +93,7 @@ void Body::fully_read(JS::Realm& realm, Web::Fetch::Infrastructure::Body::Proces auto reader = Streams::acquire_readable_stream_default_reader(m_stream); if (reader.is_exception()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), reader.release_error()); + auto throw_completion = Bindings::exception_to_throw_completion(realm.vm(), reader.release_error()); error_steps(throw_completion.release_value().value()); return; } diff --git a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp index 8e64a273904a4..84b4cdbd35f01 100644 --- a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp +++ b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp @@ -1055,7 +1055,7 @@ void HTMLParser::handle_in_head(HTMLToken& token) // If an exception is thrown, then catch it, report the exception, insert an element at the adjusted insertion location with template, and return. auto result = declarative_shadow_host_element.attach_a_shadow_root(mode, clonable, serializable, delegates_focus, Bindings::SlotAssignmentMode::Named); if (result.is_error()) { - report_exception(Bindings::dom_exception_to_throw_completion(vm(), result.release_error()), realm()); + report_exception(Bindings::exception_to_throw_completion(vm(), result.release_error()), realm()); insert_an_element_at_the_adjusted_insertion_location(template_); return; } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp index c58afdbab0276..46c9815ddb021 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp +++ b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp @@ -68,7 +68,7 @@ void ImportMapParseResult::register_import_map(Window& global) { // 1. If result's error to rethrow is not null, then report the exception given by result's error to rethrow and return. if (m_error_to_rethrow.has_value()) { - auto completion = Web::Bindings::dom_exception_to_throw_completion(global.vm(), m_error_to_rethrow.value()); + auto completion = Web::Bindings::exception_to_throw_completion(global.vm(), m_error_to_rethrow.value()); HTML::report_exception(completion, global.realm()); return; } diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 6707c8595623f..1ca54b536d996 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -423,7 +423,7 @@ class DefaultStreamTeeReadRequest final : public ReadRequest { // 2. If cloneResult is an abrupt completion, if (clone_result.is_exception()) { - auto completion = Bindings::dom_exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); + auto completion = Bindings::exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); // 1. Perform ! ReadableStreamDefaultControllerError(branch1.[[controller]], cloneResult.[[Value]]). readable_stream_default_controller_error(controller1, completion.value().value()); @@ -734,7 +734,7 @@ class ByteStreamTeeDefaultReadRequest final : public ReadRequest { // 2. If cloneResult is an abrupt completion, if (clone_result.is_exception()) { - auto completion = Bindings::dom_exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); + auto completion = Bindings::exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); // 1. Perform ! ReadableByteStreamControllerError(branch1.[[controller]], cloneResult.[[Value]]). readable_byte_stream_controller_error(controller1, completion.value().value()); @@ -898,7 +898,7 @@ class ByteStreamTeeBYOBReadRequest final : public ReadIntoRequest { // 2. If cloneResult is an abrupt completion, if (clone_result.is_exception()) { - auto completion = Bindings::dom_exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); + auto completion = Bindings::exception_to_throw_completion(m_realm->vm(), clone_result.release_error()); // 1. Perform ! ReadableByteStreamControllerError(byobBranch.[[controller]], cloneResult.[[Value]]). readable_byte_stream_controller_error(byob_controller, completion.value().value()); @@ -1857,7 +1857,7 @@ void readable_byte_stream_controller_pull_into(ReadableByteStreamController& con // 8. If bufferResult is an abrupt completion, if (buffer_result.is_exception()) { // 1. Perform readIntoRequest’s error steps, given bufferResult.[[Value]]. - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, buffer_result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, buffer_result.exception()); read_into_request.on_error(*throw_completion.release_value()); // 2. Return. @@ -4863,7 +4863,7 @@ void set_up_transform_stream_default_controller_from_transformer(TransformStream // 2. If result is an abrupt completion, return a promise rejected with result.[[Value]]. if (result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, result.exception()); return WebIDL::create_rejected_promise(realm, *throw_completion.release_value()); } @@ -4951,7 +4951,7 @@ WebIDL::ExceptionOr transform_stream_default_controller_enqueue(TransformS // 5. If enqueueResult is an abrupt completion, if (enqueue_result.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, enqueue_result.exception()); + auto throw_completion = Bindings::exception_to_throw_completion(vm, enqueue_result.exception()); // 1. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, enqueueResult.[[Value]]). transform_stream_error_writable_and_unblock_write(*stream, throw_completion.value().value()); diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index edfd0d17f21e6..be830847cdb00 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -701,7 +701,7 @@ GC::Ref compile_potential_webassembly_response(JS::VM& vm, GC:: // 8. Consume response’s body as an ArrayBuffer, and let bodyPromise be the result. auto body_promise_or_error = response_object.array_buffer(); if (body_promise_or_error.is_error()) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), body_promise_or_error.release_error()); + auto throw_completion = Bindings::exception_to_throw_completion(realm.vm(), body_promise_or_error.release_error()); WebIDL::reject_promise(realm, return_value, *throw_completion.value()); return JS::js_undefined(); } diff --git a/Libraries/LibWeb/WebIDL/Promise.cpp b/Libraries/LibWeb/WebIDL/Promise.cpp index cb2de074f69be..c7eff45aad64d 100644 --- a/Libraries/LibWeb/WebIDL/Promise.cpp +++ b/Libraries/LibWeb/WebIDL/Promise.cpp @@ -292,7 +292,7 @@ void wait_for_all(JS::Realm& realm, Vector> const& promises, Fu GC::Ref create_rejected_promise_from_exception(JS::Realm& realm, Exception exception) { - auto throw_completion = Bindings::dom_exception_to_throw_completion(realm.vm(), move(exception)); + auto throw_completion = Bindings::exception_to_throw_completion(realm.vm(), move(exception)); return WebIDL::create_rejected_promise(realm, *throw_completion.value()); } From 31d21570bfe7b4615242d393a2fe33337b5d6e10 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 9 Dec 2024 19:46:00 -0700 Subject: [PATCH 021/237] LibWeb: Don't store local functions in GC Function If a function isn't going to be escaped from the current context, there's no need to wrap the lambda in a GC allocation. --- Libraries/LibWeb/HTML/HTMLElement.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 1aabe35873853..bb61449831da7 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -1014,11 +1014,11 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except m_popover_showing_or_hiding = true; // 7. Let cleanupShowingFlag be the following steps: - auto cleanup_showing_flag = GC::create_function(this->heap(), [&nested_show, this] { + auto cleanup_showing_flag = [&nested_show, this] { // 7.1. If nestedShow is false, then set element's popover showing or hiding to false. if (!nested_show) m_popover_showing_or_hiding = false; - }); + }; // FIXME: 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return. @@ -1062,7 +1062,7 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except // FIXME: 20. Queue a popover toggle event task given element, "closed", and "open". // 21. Run cleanupShowingFlag. - cleanup_showing_flag->function()(); + cleanup_showing_flag(); return {}; } @@ -1095,14 +1095,14 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv fire_events = FireEvents::No; // 6. Let cleanupSteps be the following steps: - auto cleanup_steps = GC::create_function(this->heap(), [&nested_hide, this] { + auto cleanup_steps = [&nested_hide, this] { // 6.1. If nestedHide is false, then set element's popover showing or hiding to false. if (nested_hide) m_popover_showing_or_hiding = false; // FIXME: 6.2. If element's popover close watcher is not null, then: // FIXME: 6.2.1. Destroy element's popover close watcher. // FIXME: 6.2.2. Set element's popover close watcher to null. - }); + }; // 7. If element's popover attribute is in the auto state, then: if (popover().has_value() && popover().value() == "auto"sv) { @@ -1138,7 +1138,7 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // FIXME: 15.2. If focusPreviousElement is true and document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of element, then run the focusing steps for previouslyFocusedElement; the viewport should not be scrolled by doing this step. // 16. Run cleanupSteps. - cleanup_steps->function()(); + cleanup_steps(); return {}; } From 6ed2bf2bb1c26a8a7746f3622a9e60b56408e280 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 9 Dec 2024 19:47:09 -0700 Subject: [PATCH 022/237] LibWeb: Mark local variables captured in GC functions as ignored These variables are all captured in queued events or other event loop tasks, but are all guarded by event loop spins later in the function. The IGNORE_USE_IN_ESCAPING_LAMBDA will soon be required for all locals that are captured by ref in GC::Function as well as AK::Function. --- Libraries/LibWeb/DOM/Document.cpp | 4 ++-- Libraries/LibWeb/HTML/EventSource.cpp | 2 +- Libraries/LibWeb/HTML/Scripting/Fetching.cpp | 3 ++- Libraries/LibWeb/HTML/TraversableNavigable.cpp | 16 ++++++++-------- .../LibWeb/IndexedDB/Internal/Algorithms.cpp | 4 ++-- Libraries/LibWeb/XHR/XMLHttpRequest.cpp | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index d5e3fce132456..5a4eb93b8b841 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -3470,7 +3470,7 @@ void Document::destroy_a_document_and_its_descendants(GC::Ptr new_docum descendant_navigables.append(other_navigable); } - auto unloaded_documents_count = descendant_navigables.size() + 1; + IGNORE_USE_IN_ESCAPING_LAMBDA auto unloaded_documents_count = descendant_navigables.size() + 1; HTML::queue_global_task(HTML::Task::Source::NavigationAndTraversal, HTML::relevant_global_object(*this), GC::create_function(heap(), [&number_unloaded, this, new_document] { unload(new_document); diff --git a/Libraries/LibWeb/HTML/EventSource.cpp b/Libraries/LibWeb/HTML/EventSource.cpp index cd91883c6f8bb..79ffec187c52a 100644 --- a/Libraries/LibWeb/HTML/EventSource.cpp +++ b/Libraries/LibWeb/HTML/EventSource.cpp @@ -280,7 +280,7 @@ void EventSource::announce_the_connection() // https://html.spec.whatwg.org/multipage/server-sent-events.html#reestablish-the-connection void EventSource::reestablish_the_connection() { - bool initial_task_has_run { false }; + IGNORE_USE_IN_ESCAPING_LAMBDA bool initial_task_has_run { false }; // 1. Queue a task to run the following steps: HTML::queue_a_task(HTML::Task::Source::RemoteEvent, nullptr, nullptr, GC::create_function(heap(), [&]() { diff --git a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 66cb8d3730a5f..3658e0d8efb6e 100644 --- a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -475,7 +475,7 @@ WebIDL::ExceptionOr> fetch_a_classic_worker_imported_scri auto& vm = realm.vm(); // 1. Let response be null. - GC::Ptr response = nullptr; + IGNORE_USE_IN_ESCAPING_LAMBDA GC::Ptr response = nullptr; // 2. Let bodyBytes be null. Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes; @@ -510,6 +510,7 @@ WebIDL::ExceptionOr> fetch_a_classic_worker_imported_scri } // 5. Pause until response is not null. + // FIXME: Consider using a "response holder" to avoid needing to annotate response as IGNORE_USE_IN_ESCAPING_LAMBDA. auto& event_loop = settings_object.responsible_event_loop(); event_loop.spin_until(GC::create_function(vm.heap(), [&]() -> bool { return response; diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 9dd5d2b9f7037..132594d789c00 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -429,7 +429,7 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_ bool check_for_cancelation, IGNORE_USE_IN_ESCAPING_LAMBDA Optional source_snapshot_params, GC::Ptr initiator_to_check, - Optional user_involvement_for_navigate_events, + IGNORE_USE_IN_ESCAPING_LAMBDA Optional user_involvement_for_navigate_events, IGNORE_USE_IN_ESCAPING_LAMBDA Optional navigation_type, IGNORE_USE_IN_ESCAPING_LAMBDA SynchronousNavigation synchronous_navigation) { @@ -487,7 +487,7 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_ } // 9. Let totalChangeJobs be the size of changingNavigables. - auto total_change_jobs = changing_navigables.size(); + IGNORE_USE_IN_ESCAPING_LAMBDA auto total_change_jobs = changing_navigables.size(); // 10. Let completedChangeJobs be 0. IGNORE_USE_IN_ESCAPING_LAMBDA size_t completed_change_jobs = 0; @@ -799,7 +799,7 @@ TraversableNavigable::HistoryStepResult TraversableNavigable::apply_the_history_ })); // 15. Let totalNonchangingJobs be the size of nonchangingNavigablesThatStillNeedUpdates. - auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size(); + IGNORE_USE_IN_ESCAPING_LAMBDA auto total_non_changing_jobs = non_changing_navigables_that_still_need_updates.size(); // 16. Let completedNonchangingJobs be 0. IGNORE_USE_IN_ESCAPING_LAMBDA auto completed_non_changing_jobs = 0u; @@ -880,10 +880,10 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che documents_to_fire_beforeunload.append(navigable->active_document()); // 2. Let unloadPromptShown be false. - auto unload_prompt_shown = false; + IGNORE_USE_IN_ESCAPING_LAMBDA auto unload_prompt_shown = false; // 3. Let finalStatus be "continue". - auto final_status = CheckIfUnloadingIsCanceledResult::Continue; + IGNORE_USE_IN_ESCAPING_LAMBDA auto final_status = CheckIfUnloadingIsCanceledResult::Continue; // 4. If traversable was given, then: if (traversable) { @@ -900,7 +900,7 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che VERIFY(user_involvement_for_navigate_events.has_value()); // 2. Let eventsFired be false. - auto events_fired = false; + IGNORE_USE_IN_ESCAPING_LAMBDA auto events_fired = false; // 3. Let needsBeforeunload be true if navigablesThatNeedBeforeUnload contains traversable; otherwise false. auto it = navigables_that_need_before_unload.find_if([&traversable](GC::Root navigable) { @@ -964,10 +964,10 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che } // 5. Let totalTasks be the size of documentsThatNeedBeforeunload. - auto total_tasks = documents_to_fire_beforeunload.size(); + IGNORE_USE_IN_ESCAPING_LAMBDA auto total_tasks = documents_to_fire_beforeunload.size(); // 6. Let completedTasks be 0. - size_t completed_tasks = 0; + IGNORE_USE_IN_ESCAPING_LAMBDA size_t completed_tasks = 0; // 7. For each document of documents, queue a global task on the navigation and traversal task source given document's relevant global object to run the steps: for (auto& document : documents_to_fire_beforeunload) { diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 2a533256e4637..69cc8b227f06c 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -79,8 +79,8 @@ WebIDL::ExceptionOr> open_a_database_connection(JS::Realm& // 2. For each entry of openConnections that does not have its close pending flag set to true, // queue a task to fire a version change event named versionchange at entry with db’s version and version. - u32 events_to_fire = open_connections.size(); - u32 events_fired = 0; + IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size(); + IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0; for (auto& entry : open_connections) { if (!entry->close_pending()) { HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, entry, db, version, &events_fired]() { diff --git a/Libraries/LibWeb/XHR/XMLHttpRequest.cpp b/Libraries/LibWeb/XHR/XMLHttpRequest.cpp index 2423c890788b6..da3fdea6e8e9a 100644 --- a/Libraries/LibWeb/XHR/XMLHttpRequest.cpp +++ b/Libraries/LibWeb/XHR/XMLHttpRequest.cpp @@ -892,7 +892,7 @@ WebIDL::ExceptionOr XMLHttpRequest::send(Optional response, Variant null_or_failure_or_bytes) { @@ -927,7 +927,7 @@ WebIDL::ExceptionOr XMLHttpRequest::send(Optional Date: Mon, 9 Dec 2024 19:48:50 -0700 Subject: [PATCH 023/237] LibWeb: Remove some uses of [&] lambda captures for queued tasks Using a default reference capture for these kinds of tasks is dangerous and prone to error. Some of the variables should for sure be captured by value so that we can keep a GC object alive rather than trying to refer to stack objects. --- Libraries/LibWeb/HTML/HTMLImageElement.cpp | 4 ++-- Libraries/LibWeb/HTML/Navigable.cpp | 2 +- Libraries/LibWeb/HTML/Parser/HTMLParser.cpp | 10 +++++----- Libraries/LibWeb/HTML/TraversableNavigable.cpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Libraries/LibWeb/HTML/HTMLImageElement.cpp index c24da1e4a7531..cd6acf934cba7 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -352,8 +352,8 @@ WebIDL::ExceptionOr> HTMLImageElement::decode() const // 3. Otherwise, in parallel wait for one of the following cases to occur, and perform the corresponding actions: Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(heap(), [this, promise, &realm, &global] { - Platform::EventLoopPlugin::the().spin_until(GC::create_function(heap(), [&] { - auto queue_reject_task = [&](String const& message) { + Platform::EventLoopPlugin::the().spin_until(GC::create_function(heap(), [this, promise, &realm, &global] { + auto queue_reject_task = [promise, &realm, &global](String const& message) { queue_global_task(Task::Source::DOMManipulation, global, GC::create_function(realm.heap(), [&realm, promise, message = String(message)] { auto exception = WebIDL::EncodingError::create(realm, message); HTML::TemporaryExecutionContext context(realm); diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index 1af55cd4ba1b5..404ff4ce491a9 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -890,7 +890,7 @@ static WebIDL::ExceptionOr create_navigation } // 7. Wait until either response is non-null, or navigable's ongoing navigation changes to no longer equal navigationId. - HTML::main_thread_event_loop().spin_until(GC::create_function(vm.heap(), [&]() { + HTML::main_thread_event_loop().spin_until(GC::create_function(vm.heap(), [navigation_id, navigable, response_holder]() { if (response_holder->response() != nullptr) return true; diff --git a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp index 84b4cdbd35f01..b5610ea94a60f 100644 --- a/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp +++ b/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp @@ -298,7 +298,7 @@ void HTMLParser::the_end(GC::Ref document, GC::Ptr pa while (!document->scripts_to_execute_when_parsing_has_finished().is_empty()) { // 1. Spin the event loop until the first script in the list of scripts that will execute when the document has finished parsing // has its "ready to be parser-executed" flag set and the parser's Document has no style sheet that is blocking scripts. - main_thread_event_loop().spin_until(GC::create_function(heap, [&] { + main_thread_event_loop().spin_until(GC::create_function(heap, [document] { return document->scripts_to_execute_when_parsing_has_finished().first()->is_ready_to_be_parser_executed() && !document->has_a_style_sheet_that_is_blocking_scripts(); })); @@ -311,7 +311,7 @@ void HTMLParser::the_end(GC::Ref document, GC::Ptr pa } // 6. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following substeps: - queue_global_task(HTML::Task::Source::DOMManipulation, *document, GC::create_function(heap, [document = document] { + queue_global_task(HTML::Task::Source::DOMManipulation, *document, GC::create_function(heap, [document] { // 1. Set the Document's load timing info's DOM content loaded event start time to the current high resolution time given the Document's relevant global object. document->load_timing_info().dom_content_loaded_event_start_time = HighResolutionTime::current_high_resolution_time(relevant_global_object(*document)); @@ -329,17 +329,17 @@ void HTMLParser::the_end(GC::Ref document, GC::Ptr pa })); // 7. Spin the event loop until the set of scripts that will execute as soon as possible and the list of scripts that will execute in order as soon as possible are empty. - main_thread_event_loop().spin_until(GC::create_function(heap, [&] { + main_thread_event_loop().spin_until(GC::create_function(heap, [document] { return document->scripts_to_execute_as_soon_as_possible().is_empty(); })); // 8. Spin the event loop until there is nothing that delays the load event in the Document. - main_thread_event_loop().spin_until(GC::create_function(heap, [&] { + main_thread_event_loop().spin_until(GC::create_function(heap, [document] { return !document->anything_is_delaying_the_load_event(); })); // 9. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following steps: - queue_global_task(HTML::Task::Source::DOMManipulation, *document, GC::create_function(document->heap(), [document = document] { + queue_global_task(HTML::Task::Source::DOMManipulation, *document, GC::create_function(document->heap(), [document] { // 1. Update the current document readiness to "complete". document->update_readiness(HTML::DocumentReadyState::Complete); diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 132594d789c00..a9db18b6e6357 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -917,7 +917,7 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che // 5. Queue a global task on the navigation and traversal task source given traversable's active window to perform the following steps: VERIFY(traversable->active_window()); - queue_global_task(Task::Source::NavigationAndTraversal, *traversable->active_window(), GC::create_function(heap(), [&] { + queue_global_task(Task::Source::NavigationAndTraversal, *traversable->active_window(), GC::create_function(heap(), [needs_beforeunload, user_involvement_for_navigate_events, traversable, target_entry, &final_status, &unload_prompt_shown, &events_fired] { // 1. if needsBeforeunload is true, then: if (needs_beforeunload) { // 1. Let (unloadPromptShownForThisDocument, unloadPromptCanceledByThisDocument) be the result of running the steps to fire beforeunload given traversable's active document and false. @@ -971,7 +971,7 @@ TraversableNavigable::CheckIfUnloadingIsCanceledResult TraversableNavigable::che // 7. For each document of documents, queue a global task on the navigation and traversal task source given document's relevant global object to run the steps: for (auto& document : documents_to_fire_beforeunload) { - queue_global_task(Task::Source::NavigationAndTraversal, relevant_global_object(*document), GC::create_function(heap(), [&] { + queue_global_task(Task::Source::NavigationAndTraversal, relevant_global_object(*document), GC::create_function(heap(), [document, &final_status, &completed_tasks, &unload_prompt_shown] { // 1. Let (unloadPromptShownForThisDocument, unloadPromptCanceledByThisDocument) be the result of running the steps to fire beforeunload given document and unloadPromptShown. auto [unload_prompt_shown_for_this_document, unload_prompt_canceled_by_this_document] = document->steps_to_fire_beforeunload(unload_prompt_shown); From 2f38c83caf3227954c1536f2122eaa926e4ceb73 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 9 Dec 2024 19:49:57 -0700 Subject: [PATCH 024/237] LibGC: Mark GC::Function and create_function as ESCAPING Whenever we create a GC function, it should always be so that we can pass it to a platform event loop spin, HTML event loop spin, or some queued task on the HTML event loop. For every use case, any local variables will be out of scope by the time the function executes. --- Libraries/LibGC/Function.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibGC/Function.h b/Libraries/LibGC/Function.h index 047ea765cc549..9bc6dcf251802 100644 --- a/Libraries/LibGC/Function.h +++ b/Libraries/LibGC/Function.h @@ -17,7 +17,7 @@ class Function final : public Cell { GC_CELL(Function, Cell); public: - static Ref create(Heap& heap, AK::Function function) + static Ref create(Heap& heap, ESCAPING AK::Function function) { return heap.allocate(move(function)); } @@ -42,7 +42,7 @@ class Function final : public Cell { }; template> -static Ref> create_function(Heap& heap, Callable&& function) +static Ref> create_function(Heap& heap, ESCAPING Callable&& function) { return Function::create(heap, AK::Function { forward(function) }); } From 85b87508bf1d3e6a92deced06776ceaada19ef66 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 9 Dec 2024 19:51:05 -0700 Subject: [PATCH 025/237] ClangPlugins: Remove confusing hasType check for lambda capture types This check asserts that templated types will never escape. Which doesn't hold up at all in practice. --- Meta/Lagom/ClangPlugins/LambdaCapturePluginAction.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Meta/Lagom/ClangPlugins/LambdaCapturePluginAction.cpp b/Meta/Lagom/ClangPlugins/LambdaCapturePluginAction.cpp index a0073ee14ea03..976909bb9457a 100644 --- a/Meta/Lagom/ClangPlugins/LambdaCapturePluginAction.cpp +++ b/Meta/Lagom/ClangPlugins/LambdaCapturePluginAction.cpp @@ -84,12 +84,7 @@ class Consumer unless(hasParent( // ::operator()(...) cxxOperatorCallExpr(has(declRefExpr(to(equalsBoundNode("lambda")))))))))), - parmVarDecl( - allOf( - // It's important that the parameter has a RecordType, as a templated type can never escape its function - hasType(cxxRecordDecl()), - hasAnnotation("serenity::escaping"))) - .bind("lambda-param-ref")))), + parmVarDecl(hasAnnotation("serenity::escaping")).bind("lambda-param-ref")))), this); } From 4559af6ef553f6a96bd310060ae07c1ac7cbb53b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:18:26 +0000 Subject: [PATCH 026/237] CI: Bump JamesIves/github-pages-deploy-action from 4.6.9 to 4.7.2 Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.9 to 4.7.2. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.6.9...v4.7.2) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/libjs-test262.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/libjs-test262.yml b/.github/workflows/libjs-test262.yml index 888d310c8fe8d..6a3b07610ac38 100644 --- a/.github/workflows/libjs-test262.yml +++ b/.github/workflows/libjs-test262.yml @@ -146,7 +146,7 @@ jobs: run: ./libjs-test262/per_file_result_diff.py -o old-libjs-data/wasm/per-file-master.json -n libjs-data/wasm/per-file-master.json - name: Deploy to GitHub - uses: JamesIves/github-pages-deploy-action@v4.6.9 + uses: JamesIves/github-pages-deploy-action@v4.7.2 with: git-config-name: LadybirdBot git-config-email: ladybirdbot@ladybird.org From 02efb64e6431273a48b5d28238a50ddc2326bab0 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Tue, 10 Dec 2024 19:34:50 +1300 Subject: [PATCH 027/237] LibWeb/DOM: Implement the DOM post connection steps See: https://github.com/whatwg/dom/commit/0616094 --- Libraries/LibWeb/DOM/Node.cpp | 29 ++++++++++++++++++++++++++++- Libraries/LibWeb/DOM/Node.h | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 106aed60edd32..a57185b5a544e 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -689,12 +689,35 @@ void Node::insert_before(GC::Ref node, GC::Ptr child, bool suppress_ // 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child. if (!suppress_observers) { - queue_tree_mutation_record(move(nodes), {}, previous_sibling.ptr(), child.ptr()); + queue_tree_mutation_record(nodes, {}, previous_sibling.ptr(), child.ptr()); } // 9. Run the children changed steps for parent. children_changed(); + // 10. Let staticNodeList be a list of nodes, initially « ». + // Spec-Note: We collect all nodes before calling the post-connection steps on any one of them, instead of calling + // the post-connection steps while we’re traversing the node tree. This is because the post-connection + // steps can modify the tree’s structure, making live traversal unsafe, possibly leading to the + // post-connection steps being called multiple times on the same node. + GC::MarkedVector> static_node_list(heap()); + + // 11. For each node of nodes, in tree order: + for (auto& node : nodes) { + // 1. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree + // order, append inclusiveDescendant to staticNodeList. + node->for_each_shadow_including_inclusive_descendant([&static_node_list](Node& inclusive_descendant) { + static_node_list.append(inclusive_descendant); + return TraversalDecision::Continue; + }); + } + + // 12. For each node of staticNodeList, if node is connected, then run the post-connection steps with node. + for (auto& node : static_node_list) { + if (node->is_connected()) + node->post_connection(); + } + if (is_connected()) { // FIXME: This will need to become smarter when we implement the :has() selector. invalidate_style(StyleInvalidationReason::ParentOfInsertedNode); @@ -1201,6 +1224,10 @@ void Node::set_needs_style_update(bool value) } } +void Node::post_connection() +{ +} + void Node::inserted() { set_needs_style_update(true); diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 3399b878671cc..64499ee7c0390 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -248,6 +248,7 @@ class Node : public EventTarget { Element const* parent_element() const; virtual void inserted(); + virtual void post_connection(); virtual void removed_from(Node*); virtual void children_changed() { } virtual void adopted_from(Document&) { } From 0a216f9c147774574249c2a793e9b3d953198d10 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Tue, 10 Dec 2024 19:35:21 +1300 Subject: [PATCH 028/237] LibWeb/HTML: Use DOM's post connection steps for + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter-different.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter-different.js new file mode 100644 index 0000000000000..3274e5bfeb26c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter-different.js @@ -0,0 +1,3 @@ +"use strict"; + +window.didExecute = true; diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter.js new file mode 100644 index 0000000000000..3274e5bfeb26c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/scripting-1/the-script-element/resources/flag-setter.js @@ -0,0 +1,3 @@ +"use strict"; + +window.didExecute = true; From ac6fe2e211e5a46451ee07ca9304fb39b6e5c433 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Tue, 3 Dec 2024 20:31:14 +1300 Subject: [PATCH 029/237] LibWeb: Implement multiple import map support --- Libraries/LibWeb/Bindings/MainThreadVM.cpp | 19 ++- Libraries/LibWeb/HTML/HTMLScriptElement.cpp | 19 +-- .../LibWeb/HTML/Scripting/Environments.cpp | 19 ++- .../LibWeb/HTML/Scripting/Environments.h | 3 +- Libraries/LibWeb/HTML/Scripting/Fetching.cpp | 92 ++++++------ Libraries/LibWeb/HTML/Scripting/ImportMap.cpp | 133 ++++++++++++++++++ Libraries/LibWeb/HTML/Scripting/ImportMap.h | 1 + .../HTML/Scripting/ImportMapParseResult.cpp | 7 +- Libraries/LibWeb/HTML/UniversalGlobalScope.h | 1 + Libraries/LibWeb/HTML/Window.h | 38 ++++- .../LibWeb/Text/expected/HTML/import-maps.txt | 2 +- .../HTML/multiple-import-maps-confict.txt | 1 + .../expected/HTML/multiple-import-maps.txt | 2 + Tests/LibWeb/Text/input/HTML/import-maps-1.js | 3 + Tests/LibWeb/Text/input/HTML/import-maps-2.js | 3 + Tests/LibWeb/Text/input/HTML/import-maps.html | 2 +- Tests/LibWeb/Text/input/HTML/import-maps.js | 3 - .../HTML/multiple-import-maps-confict.html | 22 +++ .../Text/input/HTML/multiple-import-maps.html | 24 ++++ 19 files changed, 294 insertions(+), 100 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt create mode 100644 Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt create mode 100644 Tests/LibWeb/Text/input/HTML/import-maps-1.js create mode 100644 Tests/LibWeb/Text/input/HTML/import-maps-2.js delete mode 100644 Tests/LibWeb/Text/input/HTML/import-maps.js create mode 100644 Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html create mode 100644 Tests/LibWeb/Text/input/HTML/multiple-import-maps.html diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Libraries/LibWeb/Bindings/MainThreadVM.cpp index b51e961fa6a77..dd374d493a784 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -532,14 +532,11 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) } } - // 8. Disallow further import maps given moduleMapRealm. - HTML::disallow_further_import_maps(*module_map_realm); - - // 9. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]], + // 8. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]], // catching any exceptions. If they throw an exception, let resolutionError be the thrown exception. auto url = HTML::resolve_module_specifier(referencing_script, module_request.module_specifier); - // 10. If the previous step threw an exception, then: + // 9. If the previous step threw an exception, then: if (url.is_exception()) { // 1. Let completion be Completion Record { [[Type]]: throw, [[Value]]: resolutionError, [[Target]]: empty }. auto completion = exception_to_throw_completion(main_thread_vm(), url.exception()); @@ -552,19 +549,19 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) return; } - // 11. Let settingsObject be moduleMapRealm's principal realm's settings object. + // 10. Let settingsObject be moduleMapRealm's principal realm's settings object. auto& settings_object = HTML::principal_realm_settings_object(HTML::principal_realm(*module_map_realm)); - // 12. Let fetchOptions be the result of getting the descendant script fetch options given originalFetchOptions, url, and settingsObject. + // 11. Let fetchOptions be the result of getting the descendant script fetch options given originalFetchOptions, url, and settingsObject. auto fetch_options = HTML::get_descendant_script_fetch_options(original_fetch_options, url.value(), settings_object); - // 13. Let destination be "script". + // 12. Let destination be "script". auto destination = Fetch::Infrastructure::Request::Destination::Script; - // 14. Let fetchClient be moduleMapRealm's principal realm's settings object. + // 13. Let fetchClient be moduleMapRealm's principal realm's settings object. GC::Ref fetch_client { HTML::principal_realm_settings_object(HTML::principal_realm(*module_map_realm)) }; - // 14. If loadState is not undefined, then: + // 15. If loadState is not undefined, then: HTML::PerformTheFetchHook perform_fetch; if (load_state) { auto& fetch_context = static_cast(*load_state); @@ -633,7 +630,7 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) vm.pop_execution_context(); }); - // 15. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, moduleMapRealm, fetchReferrer, + // 16. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, moduleMapRealm, fetchReferrer, // moduleRequest, and onSingleFetchComplete as defined below. // If loadState is not undefined and loadState.[[PerformFetch]] is not null, pass loadState.[[PerformFetch]] along as well. HTML::fetch_single_imported_module_script(*module_map_realm, url.release_value(), *fetch_client, destination, fetch_options, *module_map_realm, fetch_referrer, module_request, perform_fetch, on_single_fetch_complete); diff --git a/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Libraries/LibWeb/HTML/HTMLScriptElement.cpp index 629e2b8b050ff..da90594c34cba 100644 --- a/Libraries/LibWeb/HTML/HTMLScriptElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLScriptElement.cpp @@ -460,25 +460,10 @@ void HTMLScriptElement::prepare_script() } // -> "importmap" else if (m_script_type == ScriptType::ImportMap) { - // FIXME: need to check if relevant global object is a Window - is this correct? - auto& global = relevant_global_object(*this); - - // 1. If el's relevant global object's import maps allowed is false, then queue an element task on the DOM manipulation task source given el to fire an event named error at el, and return. - if (is(global) && !verify_cast(global).import_maps_allowed()) { - queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { - dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); - }); - return; - } - - // 2. Set el's relevant global object's import maps allowed to false. - if (is(global)) - verify_cast(global).set_import_maps_allowed(false); - - // 3. Let result be the result of creating an import map parse result given source text and base URL. + // 1. Let result be the result of creating an import map parse result given source text and base URL. auto result = ImportMapParseResult::create(realm(), source_text.to_byte_string(), base_url); - // 4. Mark as ready el given result. + // 2. Mark as ready el given result. mark_as_ready(Result(move(result))); } } diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Libraries/LibWeb/HTML/Scripting/Environments.cpp index bedb963b21d87..1379f1d60708a 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -279,9 +279,8 @@ bool module_type_allowed(JS::Realm const&, StringView module_type) return true; } -// https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps -// https://whatpr.org/html/9893/webappapis.html#disallow-further-import-maps -void disallow_further_import_maps(JS::Realm& realm) +// https://html.spec.whatwg.org/multipage/webappapis.html#add-module-to-resolved-module-set +void add_module_to_resolved_module_set(JS::Realm& realm, String const& serialized_base_url, String const& normalized_specifier, Optional const& as_url) { // 1. Let global be realm's global object. auto& global = realm.global_object(); @@ -290,8 +289,18 @@ void disallow_further_import_maps(JS::Realm& realm) if (!is(global)) return; - // 3. Set global's import maps allowed to false. - verify_cast(global).set_import_maps_allowed(false); + // 3. Let record be a new specifier resolution record, with serialized base URL set to serializedBaseURL, + // specifier set to normalizedSpecifier, and specifier as a URL set to asURL. + // + // NOTE: We set 'specifier as a URL set to asURL' as a bool to simplify logic when merging import maps. + SpecifierResolution resolution { + .serialized_base_url = serialized_base_url, + .specifier = normalized_specifier, + .specifier_is_null_or_url_like_that_is_special = !as_url.has_value() || as_url->is_special(), + }; + + // 4. Append record to global's resolved module set. + return verify_cast(global).append_resolved_module(move(resolution)); } // https://whatpr.org/html/9893/webappapis.html#concept-realm-module-map diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.h b/Libraries/LibWeb/HTML/Scripting/Environments.h index 66e90db5c4f81..118cbfeba4964 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -139,7 +139,8 @@ void prepare_to_run_callback(JS::Realm&); void clean_up_after_running_callback(JS::Realm const&); ModuleMap& module_map_of_realm(JS::Realm&); bool module_type_allowed(JS::Realm const&, StringView module_type); -void disallow_further_import_maps(JS::Realm&); + +void add_module_to_resolved_module_set(JS::Realm&, String const& serialized_base_url, String const& normalized_specifier, Optional const& as_url); EnvironmentSettingsObject& incumbent_settings_object(); JS::Realm& incumbent_realm(); diff --git a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 3658e0d8efb6e..deecf0de04b21 100644 --- a/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -120,8 +120,8 @@ WebIDL::ExceptionOr resolve_module_specifier(Optional referri if (is(realm->global_object())) import_map = verify_cast(realm->global_object()).import_map(); - // 6. Let baseURLString be baseURL, serialized. - auto base_url_string = base_url->serialize(); + // 6. Let serializedBaseURL be baseURL, serialized. + auto serialized_base_url = base_url->serialize(); // 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL. auto as_url = resolve_url_like_module_specifier(specifier, *base_url); @@ -129,37 +129,49 @@ WebIDL::ExceptionOr resolve_module_specifier(Optional referri // 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null; otherwise, specifier. auto normalized_specifier = as_url.has_value() ? as_url->serialize().to_byte_string() : specifier; - // 9. For each scopePrefix → scopeImports of importMap's scopes: + // 9. Let result be a URL-or-null, initially null. + Optional result; + + // 10. For each scopePrefix → scopeImports of importMap's scopes: for (auto const& entry : import_map.scopes()) { // FIXME: Clarify if the serialization steps need to be run here. The steps below assume // scopePrefix to be a string. auto const& scope_prefix = entry.key.serialize(); auto const& scope_imports = entry.value; - // 1. If scopePrefix is baseURLString, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of baseURLString, then: - if (scope_prefix == base_url_string || (scope_prefix.ends_with('/') && Infra::is_code_unit_prefix(scope_prefix, base_url_string))) { + // 1. If scopePrefix is serializedBaseURL, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of serializedBaseURL, then: + if (scope_prefix == serialized_base_url || (scope_prefix.ends_with('/') && Infra::is_code_unit_prefix(scope_prefix, serialized_base_url))) { // 1. Let scopeImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and scopeImports. auto scope_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, scope_imports)); - // 2. If scopeImportsMatch is not null, then return scopeImportsMatch. - if (scope_imports_match.has_value()) - return scope_imports_match.release_value(); + // 2. If scopeImportsMatch is not null, then set result to scopeImportsMatch, and break. + if (scope_imports_match.has_value()) { + result = scope_imports_match.release_value(); + break; + } } } - // 10. Let topLevelImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and importMap's imports. - auto top_level_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, import_map.imports())); + // 11. If result is null, set result be the result of resolving an imports match given normalizedSpecifier, asURL, and importMap's imports. + if (!result.has_value()) + result = TRY(resolve_imports_match(normalized_specifier, as_url, import_map.imports())); + + // 12. If result is null, set it to asURL. + // Spec-Note: By this point, if result was null, specifier wasn't remapped to anything by importMap, but it might have been able to be turned into a URL. + if (!result.has_value()) + result = as_url; - // 11. If topLevelImportsMatch is not null, then return topLevelImportsMatch. - if (top_level_imports_match.has_value()) - return top_level_imports_match.release_value(); + // 13. If result is not null, then: + if (result.has_value()) { + // 1. Add module to resolved module set given realm, serializedBaseURL, normalizedSpecifier, and asURL. + add_module_to_resolved_module_set(*realm, serialized_base_url, MUST(String::from_byte_string(normalized_specifier)), as_url); - // 12. If asURL is not null, then return asURL. - if (as_url.has_value()) - return as_url.release_value(); + // 2. Return result. + return result.release_value(); + } - // 13. Throw a TypeError indicating that specifier was a bare specifier, but was not remapped to anything by importMap. - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Failed to resolve non relative module specifier '{}' from an import map.", specifier).release_value_but_fixme_should_propagate_errors() }; + // 14. Throw a TypeError indicating that specifier was a bare specifier, but was not remapped to anything by importMap. + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Failed to resolve non relative module specifier '{}' from an import map.", specifier)) }; } // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match @@ -292,34 +304,27 @@ ScriptFetchOptions get_descendant_script_fetch_options(ScriptFetchOptions const& // 1. Let newOptions be a copy of originalOptions. auto new_options = original_options; - // 2. Let integrity be the empty string. - String integrity; - - // 3. If settingsObject's global object is a Window object, then set integrity to the result of resolving a module integrity metadata with url and settingsObject. - if (is(settings_object.global_object())) - integrity = resolve_a_module_integrity_metadata(url, settings_object); + // 2. Let integrity be the result of resolving a module integrity metadata with url and settingsObject. + String integrity = resolve_a_module_integrity_metadata(url, settings_object); - // 4. Set newOptions's integrity metadata to integrity. + // 3. Set newOptions's integrity metadata to integrity. new_options.integrity_metadata = integrity; - // 5. Set newOptions's fetch priority to "auto". + // 4. Set newOptions's fetch priority to "auto". new_options.fetch_priority = Fetch::Infrastructure::Request::Priority::Auto; - // 6. Return newOptions. + // 5. Return newOptions. return new_options; } // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-module-integrity-metadata String resolve_a_module_integrity_metadata(const URL::URL& url, EnvironmentSettingsObject& settings_object) { - // 1. Assert: settingsObject's global object is a Window object. - VERIFY(is(settings_object.global_object())); - - // 2. Let map be settingsObject's global object's import map. - auto map = static_cast(settings_object.global_object()).import_map(); + // 1. Let map be settingsObject's global object's import map. + auto map = verify_cast(settings_object.global_object()).import_map(); - // 3. If map's integrity[url] does not exist, then return the empty string. - // 4. Return map's integrity[url]. + // 2. If map's integrity[url] does not exist, then return the empty string. + // 3. Return map's integrity[url]. return MUST(String::from_byte_string(map.integrity().get(url).value_or(""))); } @@ -860,9 +865,6 @@ void fetch_single_module_script(JS::Realm& realm, // https://whatpr.org/html/9893/webappapis.html#fetch-a-module-script-tree void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const& options, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settingsObject's realm. - disallow_further_import_maps(settings_object.realm()); - auto steps = create_on_fetch_script_complete(realm.heap(), [&realm, &settings_object, on_complete, url](auto result) mutable { // 1. If result is null, run onComplete given null, and abort these steps. if (!result) { @@ -875,27 +877,17 @@ void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, E fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); }); - // 2. Fetch a single module script given url, settingsObject, "script", options, settingsObject's realm, "client", true, and with the following steps given result: + // 1. Fetch a single module script given url, settingsObject, "script", options, settingsObject's realm, "client", true, and with the following steps given result: fetch_single_module_script(realm, url, settings_object, Fetch::Infrastructure::Request::Destination::Script, options, settings_object.realm(), Web::Fetch::Infrastructure::Request::Referrer::Client, {}, TopLevelModule::Yes, nullptr, steps); } // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph -// https://whatpr.org/html/9893/webappapis.html#fetch-an-inline-module-script-graph void fetch_inline_module_script_graph(JS::Realm& realm, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settingsObject's realm. - disallow_further_import_maps(settings_object.realm()); - - // 2. Let script be the result of creating a JavaScript module script using sourceText, settingsObject's realm, baseURL, and options. + // 1. Let script be the result of creating a JavaScript module script using sourceText, settingsObject's realm, baseURL, and options. auto script = JavaScriptModuleScript::create(filename, source_text.view(), settings_object.realm(), base_url).release_value_but_fixme_should_propagate_errors(); - // 3. If script is null, run onComplete given null, and return. - if (!script) { - on_complete->function()(nullptr); - return; - } - - // 5. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. + // 2. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp b/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp index 46c8c32cf3032..cc609e3e12f05 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp +++ b/Libraries/LibWeb/HTML/Scripting/ImportMap.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Jamie Mansfield + * Copyright (c) 2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,7 +11,9 @@ #include #include #include +#include #include +#include namespace Web::HTML { @@ -263,4 +266,134 @@ WebIDL::ExceptionOr normalize_module_integrity_map(JS::Realm return normalised; } +// https://html.spec.whatwg.org/multipage/webappapis.html#merge-module-specifier-maps +static ModuleSpecifierMap merge_module_specifier_maps(JS::Realm& realm, ModuleSpecifierMap const& new_map, ModuleSpecifierMap const& old_map) +{ + // 1. Let mergedMap be a deep copy of oldMap. + ModuleSpecifierMap merged_map = old_map; + + // 2. For each specifier → url of newMap: + for (auto const& [specifier, url] : new_map) { + // 1. If specifier exists in oldMap, then: + if (old_map.contains(specifier)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier))); + + // 2. Continue. + continue; + } + + // 2. Set mergedMap[specifier] to url. + merged_map.set(specifier, url); + } + + // 3. Return mergedMap. + return merged_map; +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#merge-existing-and-new-import-maps +void merge_existing_and_new_import_maps(Window& global, ImportMap& new_import_map) +{ + auto& realm = global.realm(); + + // 1. Let newImportMapScopes be a deep copy of newImportMap's scopes. + auto new_import_map_scopes = new_import_map.scopes(); + + // Spec-Note: We're mutating these copies and removing items from them when they are used to ignore scope-specific + // rules. This is true for newImportMapScopes, as well as to newImportMapImports below. + + // 2. Let oldImportMap be global's import map. + auto& old_import_map = global.import_map(); + + // 3. Let newImportMapImports be a deep copy of newImportMap's imports. + auto new_import_map_imports = new_import_map.imports(); + + // 4. For each scopePrefix → scopeImports of newImportMapScopes: + for (auto& [scope_prefix, scope_imports] : new_import_map_scopes) { + // 1. For each record of global's resolved module set: + for (auto const& record : global.resolved_module_set()) { + // 1. If scopePrefix is record's serialized base URL, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of record's serialized base URL, then: + if (scope_prefix == record.serialized_base_url || (scope_prefix.to_string().ends_with('/') && record.serialized_base_url.has_value() && Infra::is_code_unit_prefix(scope_prefix.to_string(), *record.serialized_base_url))) { + // 1. For each specifierKey → resolutionResult of scopeImports: + scope_imports.remove_all_matching([&](ByteString const& specifier_key, Optional const&) { + // 1. If specifierKey is record's specifier, or if all of the following conditions are true: + // * specifierKey ends with U+002F (/); + // * specifierKey is a code unit prefix of record's specifier; + // * either record's specifier as a URL is null or is special, + // then: + if (specifier_key.view() == record.specifier + || (specifier_key.ends_with('/') + && Infra::is_code_unit_prefix(specifier_key, record.specifier) + && record.specifier_is_null_or_url_like_that_is_special)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They + // may choose to avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier_key))); + + // 2. Remove scopeImports[specifierKey]. + return true; + } + + return false; + }); + } + } + + // 2. If scopePrefix exists in oldImportMap's scopes, then set oldImportMap's scopes[scopePrefix] to the result + // of merging module specifier maps, given scopeImports and oldImportMap's scopes[scopePrefix]. + if (auto it = old_import_map.scopes().find(scope_prefix); it != old_import_map.scopes().end()) { + it->value = merge_module_specifier_maps(realm, scope_imports, it->value); + } + // 3. Otherwise, set oldImportMap's scopes[scopePrefix] to scopeImports. + else { + old_import_map.scopes().set(scope_prefix, scope_imports); + } + } + + // 5. For each url → integrity of newImportMap's integrity: + for (auto const& [url, integrity] : new_import_map.integrity()) { + // 1. If url exists in oldImportMap's integrity, then: + if (old_import_map.integrity().contains(url)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map integrity rule for url '{}' was ignored as one was already present in the existing import map", url))); + + // 2. Continue. + continue; + } + + // 2. Set oldImportMap's integrity[url] to integrity. + old_import_map.integrity().set(url, integrity); + } + + // 6. For each record of global's resolved module set: + for (auto const& record : global.resolved_module_set()) { + // 1. For each specifier → url of newImportMapImports: + new_import_map_imports.remove_all_matching([&](ByteString const& specifier, Optional const&) { + // 1. If specifier starts with record's specifier, then: + if (specifier.starts_with(record.specifier)) { + // 1. The user agent may report a warning to the console indicating the ignored rule. They may choose to + // avoid reporting if the rule is identical to an existing one. + auto& console = realm.intrinsics().console_object()->console(); + console.output_debug_message(JS::Console::LogLevel::Warn, + MUST(String::formatted("An import map rule for specifier '{}' was ignored as one was already present in the existing import map", specifier))); + + // 2. Remove newImportMapImports[specifier]. + return true; + } + + return false; + }); + } + + // 7. Set oldImportMap's imports to the result of merge module specifier maps, given newImportMapImports and oldImportMap's imports. + old_import_map.set_imports(merge_module_specifier_maps(realm, new_import_map_imports, old_import_map.imports())); +} + } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMap.h b/Libraries/LibWeb/HTML/Scripting/ImportMap.h index afd99ce5d6f3f..77a133edbfde1 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMap.h +++ b/Libraries/LibWeb/HTML/Scripting/ImportMap.h @@ -44,5 +44,6 @@ Optional normalise_specifier_key(JS::Realm& realm, Deprecat WebIDL::ExceptionOr sort_and_normalise_module_specifier_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); WebIDL::ExceptionOr> sort_and_normalise_scopes(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); WebIDL::ExceptionOr normalize_module_integrity_map(JS::Realm& realm, JS::Object& original_map, URL::URL base_url); +void merge_existing_and_new_import_maps(Window&, ImportMap&); } diff --git a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp index 46c9815ddb021..f64a7c96d32f6 100644 --- a/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp +++ b/Libraries/LibWeb/HTML/Scripting/ImportMapParseResult.cpp @@ -73,12 +73,9 @@ void ImportMapParseResult::register_import_map(Window& global) return; } - // 2. Assert: global's import map is an empty import map. - VERIFY(global.import_map().imports().is_empty() && global.import_map().scopes().is_empty()); - - // 3. Set global's import map to result's import map. + // 2. Merge existing and new import maps, given global and result's import map. VERIFY(m_import_map.has_value()); - global.set_import_map(m_import_map.value()); + merge_existing_and_new_import_maps(global, m_import_map.value()); } } diff --git a/Libraries/LibWeb/HTML/UniversalGlobalScope.h b/Libraries/LibWeb/HTML/UniversalGlobalScope.h index 26ff0e38aeec8..df860fe174489 100644 --- a/Libraries/LibWeb/HTML/UniversalGlobalScope.h +++ b/Libraries/LibWeb/HTML/UniversalGlobalScope.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::HTML { diff --git a/Libraries/LibWeb/HTML/Window.h b/Libraries/LibWeb/HTML/Window.h index 0e21066b60215..6e11baca23f53 100644 --- a/Libraries/LibWeb/HTML/Window.h +++ b/Libraries/LibWeb/HTML/Window.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,25 @@ struct WindowPostMessageOptions : public StructuredSerializeOptions { String target_origin { "/"_string }; }; +// https://html.spec.whatwg.org/multipage/webappapis.html#specifier-resolution-record +// A specifier resolution record is a struct. It has the following items: +struct SpecifierResolution { + // A serialized base URL + // A string-or-null that represents the base URL of the specifier, when one exists. + Optional serialized_base_url; + + // A specifier + // A string representing the specifier. + String specifier; + + // A specifier as a URL + // A URL-or-null that represents the URL in case of a URL-like module specifier. + // + // Spec-Note: Implementations can replace specifier as a URL with a boolean that indicates + // that the specifier is either bare or URL-like that is special. + bool specifier_is_null_or_url_like_that_is_special { false }; +}; + class Window final : public DOM::EventTarget , public GlobalEventHandlers @@ -96,11 +114,12 @@ class Window final GC::Ptr navigable() const; + ImportMap& import_map() { return m_import_map; } ImportMap const& import_map() const { return m_import_map; } void set_import_map(ImportMap const& import_map) { m_import_map = import_map; } - bool import_maps_allowed() const { return m_import_maps_allowed; } - void set_import_maps_allowed(bool import_maps_allowed) { m_import_maps_allowed = import_maps_allowed; } + void append_resolved_module(SpecifierResolution resolution) { m_resolved_module_set.append(move(resolution)); } + Vector const& resolved_module_set() const { return m_resolved_module_set; } WebIDL::ExceptionOr> window_open_steps(StringView url, StringView target, StringView features); @@ -269,11 +288,18 @@ class Window final GC::Ptr m_current_event; - // https://html.spec.whatwg.org/multipage/webappapis.html#concept-window-import-map + // https://html.spec.whatwg.org/multipage/webappapis.html#concept-global-import-map + // A global object has an import map, initially an empty import map. ImportMap m_import_map; - // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed - bool m_import_maps_allowed { true }; + // https://html.spec.whatwg.org/multipage/webappapis.html#resolved-module-set + // A global object has a resolved module set, a set of specifier resolution records, initially empty. + // + // Spec-Note: The resolved module set ensures that module specifier resolution returns the same result when called + // multiple times with the same (referrer, specifier) pair. It does that by ensuring that import map rules + // that impact the specifier in its referrer's scope cannot be defined after its initial resolution. For + // now, only Window global objects have their module set data structures modified from the initial empty one. + Vector m_resolved_module_set; GC::Ptr m_screen; GC::Ptr m_navigator; diff --git a/Tests/LibWeb/Text/expected/HTML/import-maps.txt b/Tests/LibWeb/Text/expected/HTML/import-maps.txt index f8b0fc5d6a1af..a47f48aaf8b35 100644 --- a/Tests/LibWeb/Text/expected/HTML/import-maps.txt +++ b/Tests/LibWeb/Text/expected/HTML/import-maps.txt @@ -1 +1 @@ -hello, friends! +(1) hello, friends! diff --git a/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt new file mode 100644 index 0000000000000..a47f48aaf8b35 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps-confict.txt @@ -0,0 +1 @@ +(1) hello, friends! diff --git a/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt new file mode 100644 index 0000000000000..fd2cdd3268cfe --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/multiple-import-maps.txt @@ -0,0 +1,2 @@ +(1) hello, friends! +(2) hello, friends! diff --git a/Tests/LibWeb/Text/input/HTML/import-maps-1.js b/Tests/LibWeb/Text/input/HTML/import-maps-1.js new file mode 100644 index 0000000000000..c71f92d83bb60 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/import-maps-1.js @@ -0,0 +1,3 @@ +export function main() { + println("(1) hello, friends!"); +} diff --git a/Tests/LibWeb/Text/input/HTML/import-maps-2.js b/Tests/LibWeb/Text/input/HTML/import-maps-2.js new file mode 100644 index 0000000000000..842ddf9e2b10b --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/import-maps-2.js @@ -0,0 +1,3 @@ +export function main() { + println("(2) hello, friends!"); +} diff --git a/Tests/LibWeb/Text/input/HTML/import-maps.html b/Tests/LibWeb/Text/input/HTML/import-maps.html index 53b77438140d6..9298242abbdcd 100644 --- a/Tests/LibWeb/Text/input/HTML/import-maps.html +++ b/Tests/LibWeb/Text/input/HTML/import-maps.html @@ -3,7 +3,7 @@ diff --git a/Tests/LibWeb/Text/input/HTML/import-maps.js b/Tests/LibWeb/Text/input/HTML/import-maps.js deleted file mode 100644 index 116b61b85fb09..0000000000000 --- a/Tests/LibWeb/Text/input/HTML/import-maps.js +++ /dev/null @@ -1,3 +0,0 @@ -export function main() { - println("hello, friends!"); -} diff --git a/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html b/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html new file mode 100644 index 0000000000000..98be8175e422b --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/multiple-import-maps-confict.html @@ -0,0 +1,22 @@ + + + + + diff --git a/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html b/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html new file mode 100644 index 0000000000000..1aca147be96c8 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/multiple-import-maps.html @@ -0,0 +1,24 @@ + + + + + From 318fc62b533f9f223d2908996e3d202061f71245 Mon Sep 17 00:00:00 2001 From: ronak69 Date: Sat, 20 Apr 2024 10:13:39 +0000 Subject: [PATCH 030/237] LibWeb: Remember page's cursor and request change only when it changes Before, on *every* mouse-move event, `page_did_request_cursor_change()` virtual function would get called, requesting to change cursor to the event's mouse position's cursor. Now, the page keeps track of the last cursor change that was requested ("page's current cursor") and only requests cursor change again if and only if the current cursor is not already the one that is required. --- Libraries/LibWeb/Page/EventHandler.cpp | 5 ++++- Libraries/LibWeb/Page/Page.h | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index 6a6c4d7a8ecdd..913968c0e589a 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -619,7 +619,10 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP auto& page = m_navigable->page(); - page.client().page_did_request_cursor_change(hovered_node_cursor); + if (page.current_cursor() != hovered_node_cursor) { + page.set_current_cursor(hovered_node_cursor); + page.client().page_did_request_cursor_change(hovered_node_cursor); + } if (hovered_node_changed) { GC::Ptr hovered_html_element = document.hovered_node() ? document.hovered_node()->enclosing_html_element_with_attribute(HTML::AttributeNames::title) : nullptr; diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 5892ec90981c9..3200c763da9d8 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -121,6 +121,9 @@ class Page final : public JS::Cell { bool is_webdriver_active() const { return m_is_webdriver_active; } void set_is_webdriver_active(bool b) { m_is_webdriver_active = b; } + Gfx::StandardCursor current_cursor() const { return m_current_cursor; } + void set_current_cursor(Gfx::StandardCursor cursor) { m_current_cursor = cursor; } + DevicePixelPoint window_position() const { return m_window_position; } void set_window_position(DevicePixelPoint position) { m_window_position = position; } @@ -246,6 +249,8 @@ class Page final : public JS::Cell { // The webdriver-active flag is set to true when the user agent is under remote control. It is initially false. bool m_is_webdriver_active { false }; + Gfx::StandardCursor m_current_cursor { Gfx::StandardCursor::Arrow }; + DevicePixelPoint m_window_position {}; DevicePixelSize m_window_size {}; GC::Ptr> m_window_rect_observer; From d48831e893ecfb070cbe42d7a6892d9ee2e9817a Mon Sep 17 00:00:00 2001 From: ronak69 Date: Sat, 20 Apr 2024 10:14:31 +0000 Subject: [PATCH 031/237] LibWeb: Leave tooltip or unhover link only if page entered/hovered one Before, on a mouse-move event, if the hovered html element did not have a tooltip or it was not a link, `page_did_leave_tooltip_area()` and `page_did_unhover_link()` virtual functions would get called. Now, the page remembers if it is in a tooltip area or hovering a link and only informs of leaving or unhovering only if it was. --- Libraries/LibWeb/Page/EventHandler.cpp | 13 ++++++++++--- Libraries/LibWeb/Page/Page.h | 9 +++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index 913968c0e589a..eb00525694ab6 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -626,15 +626,22 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP if (hovered_node_changed) { GC::Ptr hovered_html_element = document.hovered_node() ? document.hovered_node()->enclosing_html_element_with_attribute(HTML::AttributeNames::title) : nullptr; + if (hovered_html_element && hovered_html_element->title().has_value()) { + page.set_is_in_tooltip_area(true); page.client().page_did_enter_tooltip_area(hovered_html_element->title()->to_byte_string()); - } else { + } else if (page.is_in_tooltip_area()) { + page.set_is_in_tooltip_area(false); page.client().page_did_leave_tooltip_area(); } - if (is_hovering_link) + + if (is_hovering_link) { + page.set_is_hovering_link(true); page.client().page_did_hover_link(document.parse_url(hovered_link_element->href())); - else + } else if (page.is_hovering_link()) { + page.set_is_hovering_link(false); page.client().page_did_unhover_link(); + } } return EventResult::Handled; diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 3200c763da9d8..4e0f7dfa91521 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -121,6 +121,12 @@ class Page final : public JS::Cell { bool is_webdriver_active() const { return m_is_webdriver_active; } void set_is_webdriver_active(bool b) { m_is_webdriver_active = b; } + bool is_hovering_link() const { return m_is_hovering_link; } + void set_is_hovering_link(bool b) { m_is_hovering_link = b; } + + bool is_in_tooltip_area() const { return m_is_in_tooltip_area; } + void set_is_in_tooltip_area(bool b) { m_is_in_tooltip_area = b; } + Gfx::StandardCursor current_cursor() const { return m_current_cursor; } void set_current_cursor(Gfx::StandardCursor cursor) { m_current_cursor = cursor; } @@ -249,6 +255,9 @@ class Page final : public JS::Cell { // The webdriver-active flag is set to true when the user agent is under remote control. It is initially false. bool m_is_webdriver_active { false }; + bool m_is_hovering_link { false }; + bool m_is_in_tooltip_area { false }; + Gfx::StandardCursor m_current_cursor { Gfx::StandardCursor::Arrow }; DevicePixelPoint m_window_position {}; From d5143db081f24b497c43bc4fc6c7db4e1f5812ce Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 4 Dec 2024 10:33:23 +0100 Subject: [PATCH 032/237] LibWeb: Skip node trees outside of range in insertParagraph Instead of recursively iterating all descendants of the common ancestor of the new line range that are not contained by that range, skip the entire node tree as soon as we determine they're not. --- Libraries/LibWeb/Editing/Commands.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 64f62a5640b50..8b370c382beb5 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -639,8 +639,9 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&) Vector> contained_nodes; auto common_ancestor = new_line_range->common_ancestor_container(); common_ancestor->for_each_in_subtree([&](GC::Ref child_node) { - if (new_line_range->contains_node(child_node)) - contained_nodes.append(child_node); + if (!new_line_range->contains_node(child_node)) + return TraversalDecision::SkipChildrenAndContinue; + contained_nodes.append(child_node); return TraversalDecision::Continue; }); From f88c13a58cc0620391548ca64cbe0c3ad3c2f2c3 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Sun, 8 Dec 2024 23:10:03 +0100 Subject: [PATCH 033/237] LibWeb: Prevent null deref in collapsed whitespace check The spec even warned us about the reference potentially being null. --- .../LibWeb/Editing/Internal/Algorithms.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index b7473b78b6e0b..754f03ecd3f1d 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -873,20 +873,20 @@ bool is_collapsed_whitespace_node(GC::Ref node) ancestor = ancestor->parent(); // 7. Let reference be node. - auto reference = node; + GC::Ptr reference = node; // 8. While reference is a descendant of ancestor: while (reference->is_descendant_of(*ancestor)) { // 1. Let reference be the node before it in tree order. - reference = *reference->previous_in_pre_order(); + reference = reference->previous_in_pre_order(); // 2. If reference is a block node or a br, return true. - if (is_block_node(reference) || is(*reference)) + if (is_block_node(*reference) || is(*reference)) return true; // 3. If reference is a Text node that is not a whitespace node, or is an img, break from // this loop. - if ((is(*reference) && !is_whitespace_node(reference)) || is(*reference)) + if ((is(*reference) && !is_whitespace_node(*reference)) || is(*reference)) break; } @@ -896,15 +896,19 @@ bool is_collapsed_whitespace_node(GC::Ref node) // 10. While reference is a descendant of ancestor: while (reference->is_descendant_of(*ancestor)) { // 1. Let reference be the node after it in tree order, or null if there is no such node. - reference = *reference->next_in_pre_order(); + reference = reference->next_in_pre_order(); + + // NOTE: Both steps below and the loop condition require a reference, so break if it's null. + if (!reference) + break; // 2. If reference is a block node or a br, return true. - if (is_block_node(reference) || is(*reference)) + if (is_block_node(*reference) || is(*reference)) return true; // 3. If reference is a Text node that is not a whitespace node, or is an img, break from // this loop. - if ((is(*reference) && !is_whitespace_node(reference)) || is(*reference)) + if ((is(*reference) && !is_whitespace_node(*reference)) || is(*reference)) break; } From 1c55153d4339ed3a69c41880582e1a28d9209216 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 6 Dec 2024 11:41:20 +0100 Subject: [PATCH 034/237] LibWeb: Refactor "editable" and "editing host" concepts The DOM spec defines what it means for an element to be an "editing host", and the Editing spec does the same for the "editable" concept. Replace our `Node::is_editable()` implementation with these spec-compliant algorithms. An editing host is an element that has the properties to make its contents effectively editable. Editable elements are descendants of an editing host. Concepts like the inheritable contenteditable attribute are propagated through the editable algorithm. --- Libraries/LibWeb/CSS/SelectorEngine.cpp | 2 +- Libraries/LibWeb/DOM/Document.cpp | 19 ++------ Libraries/LibWeb/DOM/Document.h | 1 - Libraries/LibWeb/DOM/EditingHostManager.cpp | 6 +-- Libraries/LibWeb/DOM/Node.cpp | 47 +++++++++++++++++-- Libraries/LibWeb/DOM/Node.h | 4 +- Libraries/LibWeb/DOM/Text.h | 4 -- Libraries/LibWeb/Editing/Commands.cpp | 2 +- Libraries/LibWeb/Editing/ExecCommand.cpp | 4 +- .../LibWeb/Editing/Internal/Algorithms.cpp | 16 +------ .../LibWeb/Editing/Internal/Algorithms.h | 1 - .../LibWeb/HTML/FormAssociatedElement.cpp | 4 +- Libraries/LibWeb/HTML/FormAssociatedElement.h | 6 +++ Libraries/LibWeb/HTML/HTMLElement.cpp | 24 ++-------- Libraries/LibWeb/HTML/HTMLElement.h | 1 - Libraries/LibWeb/HTML/HTMLInputElement.cpp | 18 ++----- Libraries/LibWeb/HTML/HTMLInputElement.h | 5 -- Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp | 5 +- Libraries/LibWeb/HTML/HTMLTextAreaElement.h | 3 -- .../LibWeb/Page/DragAndDropEventHandler.cpp | 2 +- Libraries/LibWeb/Painting/PaintableBox.cpp | 7 ++- .../LibWeb/WebDriver/ElementReference.cpp | 6 +-- 22 files changed, 85 insertions(+), 102 deletions(-) diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index fb08103f66085..420018e19e775 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -411,7 +411,7 @@ static bool matches_read_write_pseudo_class(DOM::Element const& element) return true; } // - elements that are editing hosts or editable and are neither input elements nor textarea elements - return element.is_editable(); + return element.is_editable_or_editing_host(); } // https://www.w3.org/TR/selectors-4/#open-state diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 5a4eb93b8b841..b852554b78b31 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -2000,11 +2000,6 @@ String const& Document::compat_mode() const return css1_compat; } -bool Document::is_editable() const -{ - return m_editable; -} - // https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement void Document::update_active_element() { @@ -2060,9 +2055,8 @@ void Document::set_focused_element(Element* element) if (auto* invalidation_target = find_common_ancestor(old_focused_element, m_focused_element) ?: this) invalidation_target->invalidate_style(StyleInvalidationReason::FocusedElementChange); - if (m_focused_element) { + if (m_focused_element) m_focused_element->did_receive_focus(); - } if (paintable()) paintable()->set_needs_display(); @@ -5538,7 +5532,7 @@ InputEventsTarget* Document::active_input_events_target() return static_cast(focused_element); if (is(*focused_element)) return static_cast(focused_element); - if (is(*focused_element) && static_cast(focused_element)->is_editable()) + if (focused_element->is_editable_or_editing_host()) return m_editing_host_manager; return nullptr; } @@ -5546,9 +5540,8 @@ InputEventsTarget* Document::active_input_events_target() GC::Ptr Document::cursor_position() const { auto const* focused_element = this->focused_element(); - if (!focused_element) { + if (!focused_element) return nullptr; - } Optional target {}; if (is(*focused_element)) @@ -5556,13 +5549,11 @@ GC::Ptr Document::cursor_position() const else if (is(*focused_element)) target = static_cast(*focused_element); - if (target.has_value()) { + if (target.has_value()) return target->cursor_position(); - } - if (is(*focused_element) && static_cast(focused_element)->is_editable()) { + if (focused_element->is_editable_or_editing_host()) return m_selection->cursor_position(); - } return nullptr; } diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 9c5661cf37ed0..f8cd52a6e88ba 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -335,7 +335,6 @@ class Document String const& compat_mode() const; void set_editable(bool editable) { m_editable = editable; } - virtual bool is_editable() const final; Element* focused_element() { return m_focused_element.ptr(); } Element const* focused_element() const { return m_focused_element.ptr(); } diff --git a/Libraries/LibWeb/DOM/EditingHostManager.cpp b/Libraries/LibWeb/DOM/EditingHostManager.cpp index f66486833b29b..0e1f20e7c9d48 100644 --- a/Libraries/LibWeb/DOM/EditingHostManager.cpp +++ b/Libraries/LibWeb/DOM/EditingHostManager.cpp @@ -36,14 +36,12 @@ void EditingHostManager::handle_insert(String const& data) auto selection = m_document->get_selection(); auto selection_range = selection->range(); - if (!selection_range) { + if (!selection_range) return; - } auto node = selection->anchor_node(); - if (!node || !node->is_editable()) { + if (!node || !node->is_editable_or_editing_host()) return; - } if (!is(*node)) { auto& realm = node->realm(); diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index a57185b5a544e..c349c5f6a7b5d 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2018-2024, Andreas Kling * Copyright (c) 2021-2022, Linus Groh * Copyright (c) 2021, Luke Wilde + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -44,16 +44,18 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include #include #include +#include #include #include @@ -1183,9 +1185,48 @@ void Node::set_document(Badge, Document& document) } } +// https://w3c.github.io/editing/docs/execCommand/#editable bool Node::is_editable() const { - return parent() && parent()->is_editable(); + // Something is editable if it is a node; it is not an editing host; + if (is_editing_host()) + return false; + + // it does not have a contenteditable attribute set to the false state; + if (is(this) && static_cast(*this).content_editable_state() == HTML::ContentEditableState::False) + return false; + + // its parent is an editing host or editable; + if (!parent() || !parent()->is_editable_or_editing_host()) + return false; + + // and either it is an HTML element, + if (is(this)) + return true; + + // or it is an svg or math element, + if (is(this) || is(this)) + return true; + + // or it is not an Element and its parent is an HTML element. + return !is(this) && is(parent()); +} + +// https://html.spec.whatwg.org/multipage/interaction.html#editing-host +bool Node::is_editing_host() const +{ + // NOTE: Both conditions below require this to be an HTML element. + if (!is(this)) + return false; + + // An editing host is either an HTML element with its contenteditable attribute in the true state or + // plaintext-only state, + auto state = static_cast(*this).content_editable_state(); + if (state == HTML::ContentEditableState::True || state == HTML::ContentEditableState::PlaintextOnly) + return true; + + // or a child HTML element of a Document whose design mode enabled is true. + return is(parent()) && static_cast(*parent()).design_mode_enabled_state(); } void Node::set_layout_node(Badge, GC::Ref layout_node) diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 64499ee7c0390..f1e509e872c60 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -137,7 +137,9 @@ class Node : public EventTarget { // NOTE: This is intended for the JS bindings. u16 node_type() const { return (u16)m_type; } - virtual bool is_editable() const; + bool is_editable() const; + bool is_editing_host() const; + bool is_editable_or_editing_host() const { return is_editable() || is_editing_host(); } virtual bool is_dom_node() const final { return true; } virtual bool is_html_element() const { return false; } diff --git a/Libraries/LibWeb/DOM/Text.h b/Libraries/LibWeb/DOM/Text.h index 1010858777b5d..d7ebcf038ca46 100644 --- a/Libraries/LibWeb/DOM/Text.h +++ b/Libraries/LibWeb/DOM/Text.h @@ -26,9 +26,6 @@ class Text // ^Node virtual FlyString node_name() const override { return "#text"_fly_string; } - virtual bool is_editable() const override { return m_always_editable || CharacterData::is_editable(); } - - void set_always_editable(bool b) { m_always_editable = b; } Optional max_length() const { return m_max_length; } void set_max_length(Optional max_length) { m_max_length = move(max_length); } @@ -51,7 +48,6 @@ class Text private: GC::Ptr m_owner; - bool m_always_editable { false }; Optional m_max_length {}; bool m_is_password_input { false }; }; diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 8b370c382beb5..0dd3ac71c021d 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -384,7 +384,7 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&) // 2. If the active range's start node is neither editable nor an editing host, return true. auto& active_range = *selection.range(); GC::Ptr node = active_range.start_container(); - if (!node->is_editable() && !is_editing_host(*node)) + if (!node->is_editable_or_editing_host()) return true; // 3. Let node and offset be the active range's start node and offset. diff --git a/Libraries/LibWeb/Editing/ExecCommand.cpp b/Libraries/LibWeb/Editing/ExecCommand.cpp index ae709735709dc..5d65ead33eff7 100644 --- a/Libraries/LibWeb/Editing/ExecCommand.cpp +++ b/Libraries/LibWeb/Editing/ExecCommand.cpp @@ -118,7 +118,7 @@ bool Document::query_command_enabled(FlyString const& command) // its start node is either editable or an editing host, auto start_node = active_range->start_container(); - if (!start_node->is_editable() && !Editing::is_editing_host(start_node)) + if (!start_node->is_editable_or_editing_host()) return false; // FIXME: the editing host of its start node is not an EditContext editing host, @@ -126,7 +126,7 @@ bool Document::query_command_enabled(FlyString const& command) // its end node is either editable or an editing host, auto& end_node = *active_range->end_container(); - if (!end_node.is_editable() && !Editing::is_editing_host(end_node)) + if (!end_node.is_editable_or_editing_host()) return false; // FIXME: the editing host of its end node is not an EditContext editing host, diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index 754f03ecd3f1d..daa6b975ef949 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -201,7 +201,7 @@ String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_br void canonicalize_whitespace(GC::Ref node, u32 offset, bool fix_collapsed_space) { // 1. If node is neither editable nor an editing host, abort these steps. - if (!node->is_editable() || !is_editing_host(node)) + if (!node->is_editable_or_editing_host()) return; // 2. Let start node equal node and let start offset equal offset. @@ -916,20 +916,6 @@ bool is_collapsed_whitespace_node(GC::Ref node) return false; } -// https://html.spec.whatwg.org/multipage/interaction.html#editing-host -bool is_editing_host(GC::Ref node) -{ - // An editing host is either an HTML element with its contenteditable attribute in the true - // state or plaintext-only state, or a child HTML element of a Document whose design mode - // enabled is true. - if (!is(*node)) - return false; - auto const& html_element = static_cast(*node); - return html_element.content_editable_state() == HTML::ContentEditableState::True - || html_element.content_editable_state() == HTML::ContentEditableState::PlaintextOnly - || node->document().design_mode_enabled_state(); -} - // https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents bool is_element_with_inline_contents(GC::Ref node) { diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.h b/Libraries/LibWeb/Editing/Internal/Algorithms.h index 441a708990d4b..e46b26c6c6738 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.h +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.h @@ -34,7 +34,6 @@ bool is_block_end_point(GC::Ref, u32 offset); bool is_block_node(GC::Ref); bool is_block_start_point(GC::Ref, u32 offset); bool is_collapsed_whitespace_node(GC::Ref); -bool is_editing_host(GC::Ref); bool is_element_with_inline_contents(GC::Ref); bool is_extraneous_line_break(GC::Ref); bool is_in_same_editing_host(GC::Ref, GC::Ref); diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index a187d32f11aaa..6927d7812ae2c 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -588,7 +588,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optionalis_editable()) + if (!text_node || !is_mutable()) return; String data_for_insertion = data; @@ -613,7 +613,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data) void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction) { auto text_node = form_associated_element_to_text_node(); - if (!text_node || !text_node->is_editable()) + if (!text_node || !is_mutable()) return; auto selection_start = this->selection_start(); auto selection_end = this->selection_end(); diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index 66e12a26eb754..bf2c53f74e29a 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -170,6 +170,9 @@ class FormAssociatedTextControlElement : public FormAssociatedElement bool has_scheduled_selectionchange_event() const { return m_has_scheduled_selectionchange_event; } void set_scheduled_selectionchange_event(bool value) { m_has_scheduled_selectionchange_event = value; } + bool is_mutable() const { return m_is_mutable; } + void set_is_mutable(bool is_mutable) { m_is_mutable = is_mutable; } + virtual void did_edit_text_node() = 0; virtual GC::Ptr form_associated_element_to_text_node() = 0; @@ -205,6 +208,9 @@ class FormAssociatedTextControlElement : public FormAssociatedElement // https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event bool m_has_scheduled_selectionchange_event { false }; + + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#mutability + bool m_is_mutable { true }; }; } diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index bb61449831da7..001ee6d104c00 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -86,25 +86,9 @@ void HTMLElement::set_dir(String const& dir) MUST(set_attribute(HTML::AttributeNames::dir, dir)); } -bool HTMLElement::is_editable() const -{ - switch (m_content_editable_state) { - case ContentEditableState::True: - case ContentEditableState::PlaintextOnly: - return true; - case ContentEditableState::False: - return false; - case ContentEditableState::Inherit: - return parent() && parent()->is_editable(); - default: - VERIFY_NOT_REACHED(); - } -} - bool HTMLElement::is_focusable() const { - return m_content_editable_state == ContentEditableState::True - || m_content_editable_state == ContentEditableState::PlaintextOnly; + return is_editing_host(); } // https://html.spec.whatwg.org/multipage/interaction.html#dom-iscontenteditable @@ -112,7 +96,7 @@ bool HTMLElement::is_content_editable() const { // The isContentEditable IDL attribute, on getting, must return true if the element is either an editing host or // editable, and false otherwise. - return is_editable(); + return is_editable_or_editing_host(); } StringView HTMLElement::content_editable() const @@ -1181,7 +1165,7 @@ WebIDL::ExceptionOr HTMLElement::toggle_popover(TogglePopoverOptionsOrForc void HTMLElement::did_receive_focus() { - if (m_content_editable_state != ContentEditableState::True) + if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly)) return; auto editing_host = document().editing_host_manager(); @@ -1202,7 +1186,7 @@ void HTMLElement::did_receive_focus() void HTMLElement::did_lose_focus() { - if (m_content_editable_state != ContentEditableState::True) + if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly)) return; document().editing_host_manager()->set_active_contenteditable_element(nullptr); diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index 77c67f50558ce..ac52ac3e53c89 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -73,7 +73,6 @@ class HTMLElement StringView dir() const; void set_dir(String const&); - virtual bool is_editable() const final; virtual bool is_focusable() const override; bool is_content_editable() const; StringView content_editable() const; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index bec7f08eeb19f..c4a6d14a9bd98 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -336,7 +336,7 @@ WebIDL::ExceptionOr HTMLInputElement::show_picker() // The showPicker() method steps are: // 1. If this is not mutable, then throw an "InvalidStateError" DOMException. - if (!m_is_mutable) + if (!is_mutable()) return WebIDL::InvalidStateError::create(realm(), "Element is not mutable"_string); // 2. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin, @@ -712,10 +712,7 @@ void HTMLInputElement::handle_maxlength_attribute() void HTMLInputElement::handle_readonly_attribute(Optional const& maybe_value) { // The readonly attribute is a boolean attribute that controls whether or not the user can edit the form control. When specified, the element is not mutable. - m_is_mutable = !maybe_value.has_value() || !is_allowed_to_be_readonly(m_type); - - if (m_text_node) - m_text_node->set_always_editable(m_is_mutable); + set_is_mutable(!maybe_value.has_value() || !is_allowed_to_be_readonly(m_type)); } // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3 @@ -871,12 +868,7 @@ void HTMLInputElement::create_text_input_shadow_tree() MUST(element->append_child(*m_inner_text_element)); m_text_node = realm().create(document(), move(initial_value)); - if (type_state() == TypeAttributeState::FileUpload) { - // NOTE: file upload state is mutable, but we don't allow the text node to be modifed - m_text_node->set_always_editable(false); - } else { - handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); - } + handle_readonly_attribute(attribute(HTML::AttributeNames::readonly)); if (type_state() == TypeAttributeState::Password) m_text_node->set_is_password_input({}, true); handle_maxlength_attribute(); @@ -907,7 +899,7 @@ void HTMLInputElement::create_text_input_shadow_tree() auto up_callback_function = JS::NativeFunction::create( realm(), [this](JS::VM&) { - if (m_is_mutable) { + if (is_mutable()) { MUST(step_up()); user_interaction_did_change_input_value(); } @@ -929,7 +921,7 @@ void HTMLInputElement::create_text_input_shadow_tree() auto down_callback_function = JS::NativeFunction::create( realm(), [this](JS::VM&) { - if (m_is_mutable) { + if (is_mutable()) { MUST(step_down()); user_interaction_did_change_input_value(); } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index 30369be9abe92..bf2df331779b0 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -100,8 +100,6 @@ class HTMLInputElement final bool indeterminate() const { return m_indeterminate; } void set_indeterminate(bool); - bool is_mutable() const { return m_is_mutable; } - void did_pick_color(Optional picked_color, ColorPickerUpdateState state); enum class MultipleHandling { @@ -339,9 +337,6 @@ class HTMLInputElement final // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty bool m_dirty_value { false }; - // https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-fe-mutable - bool m_is_mutable { true }; - // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior bool m_before_legacy_pre_activation_behavior_checked { false }; bool m_before_legacy_pre_activation_behavior_indeterminate { false }; diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index 6aeb1363cc3ae..89f2bdac319dc 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -394,10 +394,7 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed() void HTMLTextAreaElement::handle_readonly_attribute(Optional const& maybe_value) { // The readonly attribute is a boolean attribute that controls whether or not the user can edit the form control. When specified, the element is not mutable. - m_is_mutable = !maybe_value.has_value(); - - if (m_text_node) - m_text_node->set_always_editable(m_is_mutable); + set_is_mutable(!maybe_value.has_value()); } // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index e26905cf107aa..3053f63d2da05 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -157,9 +157,6 @@ class HTMLTextAreaElement final // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty bool m_dirty_value { false }; - // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-mutable - bool m_is_mutable { true }; - // https://html.spec.whatwg.org/multipage/form-elements.html#concept-textarea-raw-value String m_raw_value; diff --git a/Libraries/LibWeb/Page/DragAndDropEventHandler.cpp b/Libraries/LibWeb/Page/DragAndDropEventHandler.cpp index 452f77cd6b39e..7a966821fbed9 100644 --- a/Libraries/LibWeb/Page/DragAndDropEventHandler.cpp +++ b/Libraries/LibWeb/Page/DragAndDropEventHandler.cpp @@ -611,7 +611,7 @@ bool DragAndDropEventHandler::allow_text_drop(GC::Ref node) const if (!m_drag_data_store->has_text_item()) return false; - if (node->is_editable()) + if (node->is_editable_or_editing_host()) return true; if (is(*node)) diff --git a/Libraries/LibWeb/Painting/PaintableBox.cpp b/Libraries/LibWeb/Painting/PaintableBox.cpp index 1ff3e14777065..f79d1f41d07ce 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -564,7 +564,12 @@ void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintabl if (cursor_position->offset() < (unsigned)fragment.start() || cursor_position->offset() > (unsigned)(fragment.start() + fragment.length())) return; - if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable()) + auto active_element = document.active_element(); + auto active_element_is_editable = is(active_element) + && dynamic_cast(*active_element).is_mutable(); + + auto dom_node = fragment.layout_node().dom_node(); + if (!dom_node || (!dom_node->is_editable() && !active_element_is_editable)) return; auto fragment_rect = fragment.absolute_rect(); diff --git a/Libraries/LibWeb/WebDriver/ElementReference.cpp b/Libraries/LibWeb/WebDriver/ElementReference.cpp index e77518a5dee68..5d116a4edb46d 100644 --- a/Libraries/LibWeb/WebDriver/ElementReference.cpp +++ b/Libraries/LibWeb/WebDriver/ElementReference.cpp @@ -288,11 +288,7 @@ bool is_element_editable(Web::DOM::Element const& element) bool is_element_mutable(Web::DOM::Element const& element) { // Denotes elements that are editing hosts or content editable. - if (!is(element)) - return false; - - auto const& html_element = static_cast(element); - return html_element.is_editable(); + return element.is_editable_or_editing_host(); } // https://w3c.github.io/webdriver/#dfn-mutable-form-control-element From bfb87b24a3d4c7d580272bc6ea9057f7ddfbc2f7 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Mon, 9 Dec 2024 00:42:46 +0100 Subject: [PATCH 035/237] LibWeb: Use `verify_cast` in insertParagraph command No functional changes. --- Libraries/LibWeb/Editing/Commands.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 0dd3ac71c021d..8bb576d40b0ae 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -599,8 +599,7 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&) || ((new_line_range->start_container() == new_line_range->end_container() && new_line_range->start_offset() == new_line_range->end_offset() - 1) && is(*new_line_range->start_container())); - VERIFY(is(*container)); - auto& container_element = static_cast(*container); + auto& container_element = verify_cast(*container); auto new_container_name = [&] -> FlyString { // 18. If the local name of container is "h1", "h2", "h3", "h4", "h5", or "h6", and end of line is true, let new // container name be the default single-line container name. From 15e3db5932197c21194ae97aa5aece8a1e5e97b2 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 6 Dec 2024 16:29:40 +0100 Subject: [PATCH 036/237] LibWeb: Add `internals.mouseDown(x, y)` This triggers a mouse button press without the up event, allowing us to e.g. simulate a selection by moving the mouse while keeping the button depressed. --- Libraries/LibWeb/Internals/Internals.cpp | 12 ++++++++++++ Libraries/LibWeb/Internals/Internals.h | 2 ++ Libraries/LibWeb/Internals/Internals.idl | 1 + 3 files changed, 15 insertions(+) diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 0d4bd0269b285..fb2914923c579 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -124,6 +124,18 @@ void Internals::click(double x, double y, UIEvents::MouseButton button) page.handle_mouseup(position, position, button, 0, 0); } +void Internals::mouse_down(double x, double y) +{ + mouse_down(x, y, UIEvents::MouseButton::Primary); +} + +void Internals::mouse_down(double x, double y, UIEvents::MouseButton button) +{ + auto& page = internals_page(); + auto position = page.css_to_device_point({ x, y }); + page.handle_mousedown(position, position, button, 0, 0); +} + void Internals::move_pointer_to(double x, double y) { auto& page = internals_page(); diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index 0b2770b39462a..56dc5e2a01538 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -32,6 +32,7 @@ class Internals final : public Bindings::PlatformObject { void click(double x, double y); void doubleclick(double x, double y); void middle_click(double x, double y); + void mouse_down(double x, double y); void move_pointer_to(double x, double y); void wheel(double x, double y, double delta_x, double delta_y); @@ -59,6 +60,7 @@ class Internals final : public Bindings::PlatformObject { virtual void initialize(JS::Realm&) override; void click(double x, double y, UIEvents::MouseButton); + void mouse_down(double x, double y, UIEvents::MouseButton); HTML::Window& internals_window() const; Page& internals_page() const; diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index c39a71c7b6156..64a2994a655ea 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -23,6 +23,7 @@ interface Internals { undefined click(double x, double y); undefined doubleclick(double x, double y); undefined middleClick(double x, double y); + undefined mouseDown(double x, double y); undefined movePointerTo(double x, double y); undefined wheel(double x, double y, double deltaX, double deltaY); From fd949ee3ddb7776b53638c3d54f6325f4247abaf Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 6 Dec 2024 16:32:20 +0100 Subject: [PATCH 037/237] LibWeb: Only set selection focus if an associated DOM node was found The relation from a paintable to a DOM node is not always set. --- Libraries/LibWeb/Page/EventHandler.cpp | 3 +-- ...on-in-contenteditable-crash-regression.txt | 1 + ...n-in-contenteditable-crash-regression.html | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/Editing/selection-in-contenteditable-crash-regression.txt create mode 100644 Tests/LibWeb/Text/input/Editing/selection-in-contenteditable-crash-regression.html diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index eb00525694ab6..71a3282c0bba4 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -596,9 +596,8 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP if (m_in_mouse_selection) { auto hit = paint_root()->hit_test(viewport_position, Painting::HitTestType::TextCursor); if (m_mouse_selection_target) { - if (hit.has_value()) { + if (hit.has_value() && hit->paintable->dom_node()) m_mouse_selection_target->set_selection_focus(*hit->paintable->dom_node(), hit->index_in_node); - } } else { if (start_index.has_value() && hit.has_value() && hit->dom_node()) { if (auto selection = document.get_selection()) { diff --git a/Tests/LibWeb/Text/expected/Editing/selection-in-contenteditable-crash-regression.txt b/Tests/LibWeb/Text/expected/Editing/selection-in-contenteditable-crash-regression.txt new file mode 100644 index 0000000000000..6b793fa0b85d2 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Editing/selection-in-contenteditable-crash-regression.txt @@ -0,0 +1 @@ +PASS (did not crash) \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/Editing/selection-in-contenteditable-crash-regression.html b/Tests/LibWeb/Text/input/Editing/selection-in-contenteditable-crash-regression.html new file mode 100644 index 0000000000000..fd4d25ad36438 --- /dev/null +++ b/Tests/LibWeb/Text/input/Editing/selection-in-contenteditable-crash-regression.html @@ -0,0 +1,19 @@ + + +
a

b
+ From 30ec8c1d4d9312931f71ecc9dd4de29df513df4c Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 4 Dec 2024 23:08:46 +0100 Subject: [PATCH 038/237] LibWeb: Implement "delete the selection" for the editing API --- Libraries/LibWeb/Editing/Commands.cpp | 24 +- .../LibWeb/Editing/Internal/Algorithms.cpp | 656 +++++++++++++++++- .../LibWeb/Editing/Internal/Algorithms.h | 29 +- 3 files changed, 681 insertions(+), 28 deletions(-) diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 8bb576d40b0ae..6c82757209c12 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -367,8 +367,8 @@ bool command_delete_action(DOM::Document& document, String const&) // 18. Call extend(node, offset) on the context object's selection. MUST(selection.extend(*node, offset)); - // FIXME: 19. Delete the selection, with direction "backward". - delete_the_selection(selection); + // 19. Delete the selection, with direction "backward". + delete_the_selection(selection, true, true, Selection::Direction::Backwards); // 20. Return true. return true; @@ -669,28 +669,12 @@ bool command_insert_paragraph_action(DOM::Document& document, String const&) // 32. If container has no visible children, call createElement("br") on the context object, and append the result // as the last child of container. - bool has_visible_child = false; - container->for_each_child([&has_visible_child](GC::Ref child) { - if (is_visible_node(child)) { - has_visible_child = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - if (!has_visible_child) + if (!has_visible_children(*container)) MUST(container->append_child(MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)))); // 33. If new container has no visible children, call createElement("br") on the context object, and append the // result as the last child of new container. - has_visible_child = false; - new_container->for_each_child([&has_visible_child](GC::Ref child) { - if (is_visible_node(child)) { - has_visible_child = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - if (!has_visible_child) + if (!has_visible_children(*new_container)) MUST(new_container->append_child(MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)))); // 34. Call collapse(new container, 0) on the context object's selection. diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index daa6b975ef949..367086970dc54 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -19,10 +19,16 @@ #include #include #include +#include +#include +#include #include #include +#include #include +#include #include +#include namespace Web::Editing { @@ -118,6 +124,18 @@ GC::Ref block_extend_a_range(DOM::Range& range) return new_range; } +// https://w3c.github.io/editing/docs/execCommand/#block-node-of +GC::Ptr block_node_of_node(GC::Ref input_node) +{ + // 1. While node is an inline node, set node to its parent. + GC::Ptr node = input_node; + while (node && is_inline_node(*node)) + node = node->parent(); + + // 2. Return node. + return node; +} + // https://w3c.github.io/editing/docs/execCommand/#canonical-space-sequence String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end) { @@ -436,27 +454,442 @@ void canonicalize_whitespace(GC::Ref node, u32 offset, bool fix_colla } // https://w3c.github.io/editing/docs/execCommand/#delete-the-selection -void delete_the_selection(Selection::Selection const& selection) +void delete_the_selection(Selection& selection, bool block_merging, bool strip_wrappers, Selection::Direction direction) { - // FIXME: implement the spec - auto active_range = selection.range(); - if (!active_range) + auto& document = *selection.document(); + + // 1. If the active range is null, abort these steps and do nothing. + // NOTE: The selection is collapsed often in this algorithm, so we shouldn't store the active range in a variable. + auto active_range = [&selection] { return selection.range(); }; + if (!active_range()) + return; + + // 2. Canonicalize whitespace at the active range's start. + canonicalize_whitespace(active_range()->start_container(), active_range()->start_offset()); + + // 3. Canonicalize whitespace at the active range's end. + canonicalize_whitespace(active_range()->end_container(), active_range()->end_offset()); + + // 4. Let (start node, start offset) be the last equivalent point for the active range's start. + auto start = last_equivalent_point({ active_range()->start_container(), active_range()->start_offset() }); + + // 5. Let (end node, end offset) be the first equivalent point for the active range's end. + auto end = first_equivalent_point({ active_range()->end_container(), active_range()->end_offset() }); + + // 6. If (end node, end offset) is not after (start node, start offset): + auto relative_position = position_of_boundary_point_relative_to_other_boundary_point(end.node, end.offset, start.node, start.offset); + if (relative_position != DOM::RelativeBoundaryPointPosition::After) { + // 1. If direction is "forward", call collapseToStart() on the context object's selection. + if (direction == Selection::Direction::Forwards) { + MUST(selection.collapse_to_start()); + } + + // 2. Otherwise, call collapseToEnd() on the context object's selection. + else { + MUST(selection.collapse_to_end()); + } + + // 3. Abort these steps. return; - MUST(active_range->delete_contents()); + } + + // 7. If start node is a Text node and start offset is 0, set start offset to the index of start node, then set + // start node to its parent. + if (is(*start.node) && start.offset == 0 && start.node->parent()) { + start = { + *start.node->parent(), + static_cast(start.node->index()), + }; + } + + // 8. If end node is a Text node and end offset is its length, set end offset to one plus the index of end node, + // then set end node to its parent. + if (is(*end.node) && end.offset == end.node->length() && end.node->parent()) { + end = { + *end.node->parent(), + static_cast(end.node->index() + 1), + }; + } + + // 9. Call collapse(start node, start offset) on the context object's selection. + MUST(selection.collapse(start.node, start.offset)); + + // 10. Call extend(end node, end offset) on the context object's selection. + MUST(selection.extend(end.node, end.offset)); + + // 12. Let start block be the active range's start node. + GC::Ptr start_block = active_range()->start_container(); + + // 13. While start block's parent is in the same editing host and start block is an inline node, set start block to + // its parent. + while (start_block->parent() && is_in_same_editing_host(*start_block->parent(), *start_block) && is_inline_node(*start_block)) + start_block = *start_block->parent(); + + // 14. If start block is neither a block node nor an editing host, or "span" is not an allowed child of start block, + // or start block is a td or th, set start block to null. + if ((!is_block_node(*start_block) && !start_block->is_editing_host()) + || !is_allowed_child_of_node(HTML::TagNames::span, GC::Ref { *start_block }) + || is(*start_block)) + start_block = {}; + + // 15. Let end block be the active range's end node. + GC::Ptr end_block = active_range()->end_container(); + + // 16. While end block's parent is in the same editing host and end block is an inline node, set end block to its + // parent. + while (end_block->parent() && is_in_same_editing_host(*end_block->parent(), *end_block) && is_inline_node(*end_block)) + end_block = end_block->parent(); + + // 17. If end block is neither a block node nor an editing host, or "span" is not an allowed child of end block, or + // end block is a td or th, set end block to null. + if ((!is_block_node(*end_block) && !end_block->is_editing_host()) + || !is_allowed_child_of_node(HTML::TagNames::span, GC::Ref { *end_block }) + || is(*end_block)) + end_block = {}; + + // 19. Record current states and values, and let overrides be the result. + auto overrides = record_current_states_and_values(*active_range()); + + // 21. If start node and end node are the same, and start node is an editable Text node: + if (start.node == end.node && is(*start.node) && start.node->is_editable()) { + // 1. Call deleteData(start offset, end offset − start offset) on start node. + MUST(static_cast(*start.node).delete_data(start.offset, end.offset - start.offset)); + + // 2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false. + canonicalize_whitespace(start.node, start.offset, false); + + // 3. If direction is "forward", call collapseToStart() on the context object's selection. + if (direction == Selection::Direction::Forwards) { + MUST(selection.collapse_to_start()); + } + + // 4. Otherwise, call collapseToEnd() on the context object's selection. + else { + MUST(selection.collapse_to_end()); + } + + // 5. Restore states and values from overrides. + restore_states_and_values(*selection.range(), overrides); + + // 6. Abort these steps. + return; + } + + // 22. If start node is an editable Text node, call deleteData() on it, with start offset as the first argument and + // (length of start node − start offset) as the second argument. + if (is(*start.node) && start.node->is_editable()) + MUST(static_cast(*start.node).delete_data(start.offset, start.node->length() - start.offset)); + + // 23. Let node list be a list of nodes, initially empty. + Vector> node_list; + + // 24. For each node contained in the active range, append node to node list if the last member of node list (if + // any) is not an ancestor of node; node is editable; and node is not a thead, tbody, tfoot, tr, th, or td. + auto common_ancestor = active_range()->common_ancestor_container(); + common_ancestor->for_each_in_subtree([&](GC::Ref node) { + if (!active_range()->contains_node(node)) + return TraversalDecision::SkipChildrenAndContinue; + + if (!node_list.is_empty() && node_list.last()->is_ancestor_of(node)) + return TraversalDecision::SkipChildrenAndContinue; + + if (!node->is_editable()) + return TraversalDecision::Continue; + + if (!is(*node) && !is(*node) && !is(*node)) + node_list.append(node); + + return TraversalDecision::Continue; + }); + + // 25. For each node in node list: + for (auto node : node_list) { + // 1. Let parent be the parent of node. + // NOTE: All nodes in node_list are descendants of common_ancestor and as such, always have a parent. + GC::Ptr parent = *node->parent(); + + // 2. Remove node from parent. + node->remove(); + + // 3. If the block node of parent has no visible children, and parent is editable or an editing host, call + // createElement("br") on the context object and append the result as the last child of parent. + auto block_node_of_parent = block_node_of_node(*parent); + if (block_node_of_parent && !has_visible_children(*block_node_of_parent) && parent->is_editable_or_editing_host()) + MUST(parent->append_child(MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)))); + + // 4. If strip wrappers is true or parent is not an inclusive ancestor of start node, while parent is an + // editable inline node with length 0, let grandparent be the parent of parent, then remove parent from + // grandparent, then set parent to grandparent. + if (strip_wrappers || !parent->is_inclusive_ancestor_of(start.node)) { + while (parent->parent() && parent->is_editable() && is_inline_node(*parent) && parent->length() == 0) { + auto grandparent = parent->parent(); + parent->remove(); + parent = grandparent; + } + } + } + + // 26. If end node is an editable Text node, call deleteData(0, end offset) on it. + if (end.node->is_editable() && is(*end.node)) + MUST(static_cast(*end.node).delete_data(0, end.offset)); + + // 27. Canonicalize whitespace at the active range's start, with fix collapsed space false. + canonicalize_whitespace(active_range()->start_container(), active_range()->start_offset(), false); + + // 28. Canonicalize whitespace at the active range's end, with fix collapsed space false. + canonicalize_whitespace(active_range()->end_container(), active_range()->end_offset(), false); + + // 30. If block merging is false, or start block or end block is null, or start block is not in the same editing + // host as end block, or start block and end block are the same: + if (!block_merging || !start_block || !end_block || !is_in_same_editing_host(*start_block, *end_block) || start_block == end_block) { + // 1. If direction is "forward", call collapseToStart() on the context object's selection. + if (direction == Selection::Direction::Forwards) { + MUST(selection.collapse_to_start()); + } + + // 2. Otherwise, call collapseToEnd() on the context object's selection. + else { + MUST(selection.collapse_to_end()); + } + + // 3. Restore states and values from overrides. + restore_states_and_values(*selection.range(), overrides); + + // 4. Abort these steps. + return; + } + + // 31. If start block has one child, which is a collapsed block prop, remove its child from it. + if (start_block->child_count() == 1 && is_collapsed_block_prop(*start_block->first_child())) + start_block->first_child()->remove(); + + // 32. If start block is an ancestor of end block: + Vector values; + if (start_block->is_ancestor_of(*end_block)) { + // 1. Let reference node be end block. + auto reference_node = end_block; + + // 2. While reference node is not a child of start block, set reference node to its parent. + while (reference_node->parent() && reference_node->parent() != start_block.ptr()) + reference_node = reference_node->parent(); + + // 3. Call collapse() on the context object's selection, with first argument start block and second argument the + // index of reference node. + MUST(selection.collapse(start_block, reference_node->index())); + + // 4. If end block has no children: + if (!end_block->has_children()) { + // 1. While end block is editable and is the only child of its parent and is not a child of start block, let + // parent equal end block, then remove end block from parent, then set end block to parent. + while (end_block->parent() && end_block->is_editable() && end_block->parent()->child_count() == 1 && end_block->parent() != start_block.ptr()) { + // AD-HOC: Set end_block's parent instead of end_block itself. + // See: https://github.com/w3c/editing/issues/473 + auto parent = end_block->parent(); + end_block->remove(); + end_block = parent; + } + + // 2. If end block is editable and is not an inline node, and its previousSibling and nextSibling are both + // inline nodes, call createElement("br") on the context object and insert it into end block's parent + // immediately after end block. + if (end_block->is_editable() && !is_inline_node(*end_block) && end_block->previous_sibling() && end_block->next_sibling() + && is_inline_node(*end_block->previous_sibling()) && is_inline_node(*end_block->next_sibling())) { + auto br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); + end_block->parent()->insert_before(br, end_block->next_sibling()); + } + + // 3. If end block is editable, remove it from its parent. + if (end_block->is_editable()) + end_block->remove(); + + // 4. Restore states and values from overrides. + restore_states_and_values(*active_range(), overrides); + + // 5. Abort these steps. + return; + } + + // 5. If end block's firstChild is not an inline node, restore states and values from record, then abort these + // steps. + if (!is_inline_node(*end_block->first_child())) { + restore_states_and_values(*active_range(), overrides); + return; + } + + // 6. Let children be a list of nodes, initially empty. + Vector> children; + + // 7. Append the first child of end block to children. + children.append(*end_block->first_child()); + + // 8. While children's last member is not a br, and children's last member's nextSibling is an inline node, + // append children's last member's nextSibling to children. + while (!is(*children.last()) && children.last()->next_sibling()) { + GC::Ref next_sibling = *children.last()->next_sibling(); + if (!is_inline_node(next_sibling)) + break; + children.append(next_sibling); + } + + // 9. Record the values of children, and let values be the result. + values = record_the_values_of_nodes(children); + + // 10. While children's first member's parent is not start block, split the parent of children. + while (children.first()->parent() != start_block) + split_the_parent_of_nodes(children); + + // 11. If children's first member's previousSibling is an editable br, remove that br from its parent. + if (is(children.first()->previous_sibling()) && children.first()->previous_sibling()->is_editable()) + children.first()->previous_sibling()->remove(); + } + + // 33. Otherwise, if start block is a descendant of end block: + else if (start_block->is_descendant_of(*end_block)) { + // 1. Call collapse() on the context object's selection, with first argument start block and second argument + // start block's length. + MUST(selection.collapse(start_block, start_block->length())); + + // 2. Let reference node be start block. + auto reference_node = start_block; + + // 3. While reference node is not a child of end block, set reference node to its parent. + while (reference_node->parent() && reference_node->parent() != end_block) + reference_node = reference_node->parent(); + + // 4. If reference node's nextSibling is an inline node and start block's lastChild is a br, remove start + // block's lastChild from it. + if (reference_node->next_sibling() && is_inline_node(*reference_node->next_sibling()) + && is(start_block->last_child())) + start_block->last_child()->remove(); + + // 5. Let nodes to move be a list of nodes, initially empty. + Vector> nodes_to_move; + + // 6. If reference node's nextSibling is neither null nor a block node, append it to nodes to move. + if (reference_node->next_sibling() && !is_block_node(*reference_node->next_sibling())) + nodes_to_move.append(*reference_node->next_sibling()); + + // 7. While nodes to move is nonempty and its last member isn't a br and its last member's nextSibling is + // neither null nor a block node, append its last member's nextSibling to nodes to move. + while (!nodes_to_move.is_empty() && !is(*nodes_to_move.last()) + && nodes_to_move.last()->next_sibling() && !is_block_node(*nodes_to_move.last()->next_sibling())) + nodes_to_move.append(*nodes_to_move.last()->next_sibling()); + + // 8. Record the values of nodes to move, and let values be the result. + values = record_the_values_of_nodes(nodes_to_move); + + // 9. For each node in nodes to move, append node as the last child of start block, preserving ranges. + auto new_position = start_block->length(); + for (auto node : nodes_to_move) + move_node_preserving_ranges(node, *start_block, new_position++); + } + + // 34. Otherwise: + else { + // 1. Call collapse() on the context object's selection, with first argument start block and second argument + // start block's length. + MUST(selection.collapse(start_block, start_block->length())); + + // 2. If end block's firstChild is an inline node and start block's lastChild is a br, remove start block's + // lastChild from it. + if (end_block->first_child() && is_inline_node(*end_block->first_child()) + && start_block->last_child() && is(*start_block->last_child())) + start_block->last_child()->remove(); + + // 3. Record the values of end block's children, and let values be the result. + Vector> end_block_children; + end_block_children.ensure_capacity(end_block->child_count()); + end_block->for_each_child([&end_block_children](auto& child) { + end_block_children.append(child); + return IterationDecision::Continue; + }); + values = record_the_values_of_nodes(end_block_children); + + // 4. While end block has children, append the first child of end block to start block, preserving ranges. + auto new_position = start_block->length(); + while (end_block->has_children()) + move_node_preserving_ranges(*end_block->first_child(), *start_block, new_position++); + + // 5. While end block has no children, let parent be the parent of end block, then remove end block from parent, + // then set end block to parent. + while (end_block->parent() && !end_block->has_children()) { + GC::Ptr parent = end_block->parent(); + end_block->remove(); + end_block = parent; + } + } + + // 36. Let ancestor be start block. + auto ancestor = start_block; + + // 37. While ancestor has an inclusive ancestor ol in the same editing host whose nextSibling is also an ol in the + // same editing host, or an inclusive ancestor ul in the same editing host whose nextSibling is also a ul in the + // same editing host: + while (true) { + auto inclusive_ancestor = ancestor; + bool has_valid_ol_or_ul_ancestor = false; + while (inclusive_ancestor) { + if (inclusive_ancestor->next_sibling() && is_in_same_editing_host(*ancestor, *inclusive_ancestor) + && is_in_same_editing_host(*inclusive_ancestor, *inclusive_ancestor->next_sibling()) + && ((is(*inclusive_ancestor) && is(*inclusive_ancestor->next_sibling())) + || (is(*inclusive_ancestor) && is(*inclusive_ancestor->next_sibling())))) { + has_valid_ol_or_ul_ancestor = true; + break; + } + inclusive_ancestor = inclusive_ancestor->parent(); + } + if (!has_valid_ol_or_ul_ancestor) + break; + + // 1. While ancestor and its nextSibling are not both ols in the same editing host, and are also not both uls in + // the same editing host, set ancestor to its parent. + while (ancestor->parent()) { + if (ancestor->next_sibling() && is_in_same_editing_host(*ancestor, *ancestor->next_sibling())) { + if (is(*ancestor) && is(*ancestor->next_sibling())) + break; + if (is(*ancestor) && is(*ancestor->next_sibling())) + break; + } + ancestor = ancestor->parent(); + } + + // 2. While ancestor's nextSibling has children, append ancestor's nextSibling's firstChild as the last child of + // ancestor, preserving ranges. + auto new_position = ancestor->length(); + while (ancestor->next_sibling()->has_children()) + move_node_preserving_ranges(*ancestor->next_sibling()->first_child(), *ancestor, new_position++); + + // 3. Remove ancestor's nextSibling from its parent. + ancestor->next_sibling()->remove(); + } + + // 38. Restore the values from values. + restore_the_values_of_nodes(values); + + // 39. If start block has no children, call createElement("br") on the context object and append the result as the + // last child of start block. + if (!start_block->has_children()) + MUST(start_block->append_child(MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)))); + + // 40. Remove extraneous line breaks at the end of start block. + remove_extraneous_line_breaks_at_the_end_of_node(*start_block); + + // 41. Restore states and values from overrides. + restore_states_and_values(*active_range(), overrides); } // https://w3c.github.io/editing/docs/execCommand/#editing-host-of GC::Ptr editing_host_of_node(GC::Ref node) { // node itself, if node is an editing host; - if (is_editing_host(node)) + if (node->is_editing_host()) return node; // or the nearest ancestor of node that is an editing host, if node is editable. if (node->is_editable()) { auto* ancestor = node->parent(); while (ancestor) { - if (is_editing_host(*ancestor)) + if (ancestor->is_editing_host()) return ancestor; ancestor = ancestor->parent(); } @@ -467,6 +900,22 @@ GC::Ptr editing_host_of_node(GC::Ref node) return {}; } +// https://w3c.github.io/editing/docs/execCommand/#first-equivalent-point +BoundaryPoint first_equivalent_point(BoundaryPoint boundary_point) +{ + // 1. While (node, offset)'s previous equivalent point is not null, set (node, offset) to its previous equivalent + // point. + while (true) { + auto previous_point = previous_equivalent_point(boundary_point); + if (!previous_point.has_value()) + break; + boundary_point = previous_point.release_value(); + } + + // 2. Return (node, offset). + return boundary_point; +} + // https://w3c.github.io/editing/docs/execCommand/#fix-disallowed-ancestors void fix_disallowed_ancestors_of_node(GC::Ref node) { @@ -845,6 +1294,56 @@ bool is_block_start_point(GC::Ref node, u32 offset) && (is_block_node(*offset_minus_one_child) || is(*offset_minus_one_child)); } +// https://w3c.github.io/editing/docs/execCommand/#collapsed-block-prop +bool is_collapsed_block_prop(GC::Ref node) +{ + // A collapsed block prop is either a collapsed line break that is not an extraneous line break, + if (is_collapsed_line_break(node) && !is_extraneous_line_break(node)) + return true; + + // or an Element that is an inline node + if (!is(*node) || !is_inline_node(node)) + return false; + + // and whose children are all either invisible or collapsed block props + bool children_all_invisible_or_collapsed = true; + bool has_collapsed_block_prop = false; + node->for_each_child([&](GC::Ref child) { + auto child_is_collapsed_block_prop = is_collapsed_block_prop(child); + if (!is_invisible_node(child) && !child_is_collapsed_block_prop) { + children_all_invisible_or_collapsed = false; + return IterationDecision::Break; + } + if (child_is_collapsed_block_prop) + has_collapsed_block_prop = true; + return IterationDecision::Continue; + }); + if (!children_all_invisible_or_collapsed) + return false; + + // and that has at least one child that is a collapsed block prop. + return has_collapsed_block_prop; +} + +// https://w3c.github.io/editing/docs/execCommand/#collapsed-line-break +bool is_collapsed_line_break(GC::Ref node) +{ + // A collapsed line break is a br + if (!is(*node)) + return false; + + // that begins a line box which has nothing else in it, and therefore has zero height. + auto layout_node = node->layout_node(); + if (!layout_node) + return false; + VERIFY(is(*layout_node)); + + // NOTE: We do not generate a TextNode for empty text after the break, so if we do not have a sibling or if that + // sibling is not a TextNode, we consider it a collapsed line break. + auto* next_layout_node = layout_node->next_sibling(); + return !is(next_layout_node); +} + // https://w3c.github.io/editing/docs/execCommand/#collapsed-whitespace-node bool is_collapsed_whitespace_node(GC::Ref node) { @@ -1194,6 +1693,21 @@ bool is_whitespace_node(GC::Ref node) return false; } +// https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point +BoundaryPoint last_equivalent_point(BoundaryPoint boundary_point) +{ + // 1. While (node, offset)'s next equivalent point is not null, set (node, offset) to its next equivalent point. + while (true) { + auto next_point = next_equivalent_point(boundary_point); + if (!next_point.has_value()) + break; + boundary_point = next_point.release_value(); + } + + // 2. Return (node, offset). + return boundary_point; +} + // https://w3c.github.io/editing/docs/execCommand/#preserving-ranges void move_node_preserving_ranges(GC::Ref node, GC::Ref new_parent, u32 new_index) { @@ -1227,6 +1741,30 @@ void move_node_preserving_ranges(GC::Ref node, GC::Ref new // subtract one from its offset. } +// https://w3c.github.io/editing/docs/execCommand/#next-equivalent-point +Optional next_equivalent_point(BoundaryPoint boundary_point) +{ + // 1. If node's length is zero, return null. + auto node = boundary_point.node; + auto node_length = node->length(); + if (node_length == 0) + return {}; + + // 3. If offset is node's length, and node's parent is not null, and node is an inline node, return (node's parent, + // 1 + node's index). + if (boundary_point.offset == node_length && node->parent() && is_inline_node(*node)) + return BoundaryPoint { *node->parent(), static_cast(node->index() + 1) }; + + // 5. If node has a child with index offset, and that child's length is not zero, and that child is an inline node, + // return (that child, 0). + auto child_at_offset = node->child_at_index(boundary_point.offset); + if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset)) + return BoundaryPoint { *child_at_offset, 0 }; + + // 7. Return null. + return {}; +} + // https://w3c.github.io/editing/docs/execCommand/#normalize-sublists void normalize_sublists_in_node(GC::Ref item) { @@ -1304,6 +1842,55 @@ bool precedes_a_line_break(GC::Ref node) return true; } +// https://w3c.github.io/editing/docs/execCommand/#previous-equivalent-point +Optional previous_equivalent_point(BoundaryPoint boundary_point) +{ + // 1. If node's length is zero, return null. + auto node = boundary_point.node; + auto node_length = node->length(); + if (node_length == 0) + return {}; + + // 2. If offset is 0, and node's parent is not null, and node is an inline node, return (node's parent, node's + // index). + if (boundary_point.offset == 0 && node->parent() && is_inline_node(*node)) + return BoundaryPoint { *node->parent(), static_cast(node->index()) }; + + // 3. If node has a child with index offset − 1, and that child's length is not zero, and that child is an inline + // node, return (that child, that child's length). + auto child_at_offset = node->child_at_index(boundary_point.offset - 1); + if (child_at_offset && child_at_offset->length() != 0 && is_inline_node(*child_at_offset)) + return BoundaryPoint { *child_at_offset, static_cast(child_at_offset->length()) }; + + // 4. Return null. + return {}; +} + +// https://w3c.github.io/editing/docs/execCommand/#record-current-states-and-values +Vector record_current_states_and_values(GC::Ref) +{ + // 1. Let overrides be a list of (string, string or boolean) ordered pairs, initially empty. + Vector overrides; + + // FIXME: 2. Let node be the first formattable node effectively contained in the active range, or null if there is none. + + // FIXME: 3. If node is null, return overrides. + + // FIXME: 4. Add ("createLink", node's effective command value for "createLink") to overrides. + + // FIXME: 5. For each command in the list "bold", "italic", "strikethrough", "subscript", "superscript", "underline", in + // order: if node's effective command value for command is one of its inline command activated values, add + // (command, true) to overrides, and otherwise add (command, false) to overrides. + + // FIXME: 6. For each command in the list "fontName", "foreColor", "hiliteColor", in order: add (command, command's value) + // to overrides. + + // FIXME: 7. Add ("fontSize", node's effective command value for "fontSize") to overrides. + + // 8. Return overrides. + return overrides; +} + // https://w3c.github.io/editing/docs/execCommand/#record-the-values Vector record_the_values_of_nodes(Vector> const& node_list) { @@ -1426,6 +2013,48 @@ void remove_node_preserving_its_descendants(GC::Ref node) node->remove(); } +// https://w3c.github.io/editing/docs/execCommand/#restore-states-and-values +void restore_states_and_values(GC::Ref, Vector const& overrides) +{ + // FIXME: 1. Let node be the first formattable node effectively contained in the active range, or null if there is none. + + // FIXME: 2. If node is not null, then for each (command, override) pair in overrides, in order: + { + // FIXME: 1. If override is a boolean, and queryCommandState(command) returns something different from override, take + // the action for command, with value equal to the empty string. + + // FIXME: 2. Otherwise, if override is a string, and command is neither "createLink" nor "fontSize", and + // queryCommandValue(command) returns something not equivalent to override, take the action for command, with + // value equal to override. + + // FIXME: 3. Otherwise, if override is a string; and command is "createLink"; and either there is a value override for + // "createLink" that is not equal to override, or there is no value override for "createLink" and node's + // effective command value for "createLink" is not equal to override: take the action for "createLink", with + // value equal to override. + + // FIXME: 4. Otherwise, if override is a string; and command is "fontSize"; and either there is a value override for + // "fontSize" that is not equal to override, or there is no value override for "fontSize" and node's + // effective command value for "fontSize" is not loosely equivalent to override: + { + // FIXME: 1. Convert override to an integer number of pixels, and set override to the legacy font size for the + // result. + + // FIXME: 2. Take the action for "fontSize", with value equal to override. + } + + // FIXME: 5. Otherwise, continue this loop from the beginning. + + // FIXME: 6. Set node to the first formattable node effectively contained in the active range, if there is one. + } + + // 3. Otherwise, for each (command, override) pair in overrides, in order: + for ([[maybe_unused]] auto const& override : overrides) { + // FIXME: 1. If override is a boolean, set the state override for command to override. + + // FIXME: 2. If override is a string, set the value override for command to override. + } +} + // https://w3c.github.io/editing/docs/execCommand/#restore-the-values void restore_the_values_of_nodes(Vector const& values) { @@ -1865,6 +2494,19 @@ GC::Ptr wrap( return new_parent; } +bool has_visible_children(GC::Ref node) +{ + bool has_visible_child = false; + node->for_each_child([&has_visible_child](GC::Ref child) { + if (is_visible_node(child)) { + has_visible_child = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return has_visible_child; +} + bool is_heading(FlyString const& local_name) { return local_name.is_one_of( diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.h b/Libraries/LibWeb/Editing/Internal/Algorithms.h index e46b26c6c6738..78a42b436983b 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.h +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.h @@ -8,6 +8,7 @@ #include #include +#include namespace Web::Editing { @@ -18,14 +19,32 @@ struct RecordedNodeValue { Optional specified_command_value; }; +// https://w3c.github.io/editing/docs/execCommand/#record-current-states-and-values +struct RecordedOverride { + FlyString const& command; + Variant value; +}; + +using Selection::Selection; + +// https://dom.spec.whatwg.org/#concept-range-bp +// FIXME: This should be defined by DOM::Range +struct BoundaryPoint { + GC::Ref node; + WebIDL::UnsignedLong offset; +}; + // Below algorithms are specified here: // https://w3c.github.io/editing/docs/execCommand/#assorted-common-algorithms GC::Ref block_extend_a_range(DOM::Range&); +GC::Ptr block_node_of_node(GC::Ref); String canonical_space_sequence(u32 length, bool non_breaking_start, bool non_breaking_end); void canonicalize_whitespace(GC::Ref, u32 offset, bool fix_collapsed_space = true); -void delete_the_selection(Selection::Selection const&); +void delete_the_selection(Selection&, bool block_merging = true, bool strip_wrappers = true, + Selection::Direction direction = Selection::Direction::Forwards); GC::Ptr editing_host_of_node(GC::Ref); +BoundaryPoint first_equivalent_point(BoundaryPoint); void fix_disallowed_ancestors_of_node(GC::Ref); bool follows_a_line_break(GC::Ref); bool is_allowed_child_of_node(Variant, FlyString> child, Variant, FlyString> parent); @@ -33,6 +52,8 @@ bool is_block_boundary_point(GC::Ref, u32 offset); bool is_block_end_point(GC::Ref, u32 offset); bool is_block_node(GC::Ref); bool is_block_start_point(GC::Ref, u32 offset); +bool is_collapsed_block_prop(GC::Ref); +bool is_collapsed_line_break(GC::Ref); bool is_collapsed_whitespace_node(GC::Ref); bool is_element_with_inline_contents(GC::Ref); bool is_extraneous_line_break(GC::Ref); @@ -46,14 +67,19 @@ bool is_prohibited_paragraph_child_name(FlyString const&); bool is_single_line_container(GC::Ref); bool is_visible_node(GC::Ref); bool is_whitespace_node(GC::Ref); +BoundaryPoint last_equivalent_point(BoundaryPoint); void move_node_preserving_ranges(GC::Ref, GC::Ref new_parent, u32 new_index); +Optional next_equivalent_point(BoundaryPoint); void normalize_sublists_in_node(GC::Ref); bool precedes_a_line_break(GC::Ref); +Optional previous_equivalent_point(BoundaryPoint); +Vector record_current_states_and_values(GC::Ref); Vector record_the_values_of_nodes(Vector> const&); void remove_extraneous_line_breaks_at_the_end_of_node(GC::Ref); void remove_extraneous_line_breaks_before_node(GC::Ref); void remove_extraneous_line_breaks_from_a_node(GC::Ref); void remove_node_preserving_its_descendants(GC::Ref); +void restore_states_and_values(GC::Ref, Vector const&); void restore_the_values_of_nodes(Vector const&); GC::Ref set_the_tag_name(GC::Ref, FlyString const&); Optional specified_command_value(GC::Ref, FlyString const& command); @@ -62,6 +88,7 @@ GC::Ptr wrap(Vector>, Function); bool is_heading(FlyString const&); } From 023c3aa5b091605316bf87a9fc516e54d03e5874 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Mon, 9 Dec 2024 22:04:12 +0000 Subject: [PATCH 039/237] LibWeb: Respect subarrays in Crypto#getRandomBytes It is the responsibility of code that deals with TypedArrays to apply the byte offset and byte length. Not doing this caused Unity Web to crash, as they call getRandomValues with views into their full main memory. Previously, it would fill their entire memory of about 33.5 MB with random bytes. --- Libraries/LibWeb/Crypto/Crypto.cpp | 2 +- .../Crypto-getRandomValues-respects-subarrays.txt | 2 ++ .../Crypto-getRandomValues-respects-subarrays.html | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/Crypto/Crypto-getRandomValues-respects-subarrays.txt create mode 100644 Tests/LibWeb/Text/input/Crypto/Crypto-getRandomValues-respects-subarrays.html diff --git a/Libraries/LibWeb/Crypto/Crypto.cpp b/Libraries/LibWeb/Crypto/Crypto.cpp index 0089b463e9ae7..24740bb3765f2 100644 --- a/Libraries/LibWeb/Crypto/Crypto.cpp +++ b/Libraries/LibWeb/Crypto/Crypto.cpp @@ -67,7 +67,7 @@ WebIDL::ExceptionOr> Crypto::get_random_values // FIXME: Handle SharedArrayBuffers // 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type. - fill_with_random(array->viewed_array_buffer()->buffer()); + fill_with_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length())); // 4. Return array. return array; diff --git a/Tests/LibWeb/Text/expected/Crypto/Crypto-getRandomValues-respects-subarrays.txt b/Tests/LibWeb/Text/expected/Crypto/Crypto-getRandomValues-respects-subarrays.txt new file mode 100644 index 0000000000000..e0a8ef819ca18 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Crypto/Crypto-getRandomValues-respects-subarrays.txt @@ -0,0 +1,2 @@ +Is first 2 bytes still 0x41? true +Is last 6 bytes still 0x41? true diff --git a/Tests/LibWeb/Text/input/Crypto/Crypto-getRandomValues-respects-subarrays.html b/Tests/LibWeb/Text/input/Crypto/Crypto-getRandomValues-respects-subarrays.html new file mode 100644 index 0000000000000..e5ac90ba033be --- /dev/null +++ b/Tests/LibWeb/Text/input/Crypto/Crypto-getRandomValues-respects-subarrays.html @@ -0,0 +1,11 @@ + + + From f2eaf3381f81dffca80a6ad0f652bd31b38bc14e Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Mon, 9 Dec 2024 11:47:14 -0800 Subject: [PATCH 040/237] LibWeb: Throw TypeError for `new Headers(null)` The WebIDL for the `Headers` constructor specifies that the `init` parameter is optional and must be of type `HeadersInit`. While the parameter can be omitted (or explicitly set to `undefined`), `null` is not a valid value. This change fixes at least 2 "Create headers with null should throw" WPT subtests which I have imported in this patch. --- .../BindingsGenerator/IDLGenerators.cpp | 12 +- .../fetch/api/headers/headers-basic.any.txt | 28 ++ .../fetch/api/headers/headers-record.any.txt | 18 + .../fetch/api/headers/headers-basic.any.html | 15 + .../fetch/api/headers/headers-basic.any.js | 275 ++++++++++++++ .../fetch/api/headers/headers-record.any.html | 15 + .../fetch/api/headers/headers-record.any.js | 357 ++++++++++++++++++ 7 files changed, 717 insertions(+), 3 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-record.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.js diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 748e2ba241f40..5b4da377ae92b 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -1550,14 +1550,20 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter @union_type@ @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { - if (!optional_default_value.has_value() || optional_default_value == "null"sv) { + if (!optional_default_value.has_value()) { union_generator.append(R"~~~( Optional<@union_type@> @cpp_name@; - if (!@js_name@@js_suffix@.is_nullish()) + if (!@js_name@@js_suffix@.is_undefined()) @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { - if (optional_default_value == "\"\"") { + if (optional_default_value == "null"sv) { + union_generator.append(R"~~~( + Optional<@union_type@> @cpp_name@; + if (!@js_name@@js_suffix@.is_nullish()) + @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); +)~~~"); + } else if (optional_default_value == "\"\"") { union_generator.append(R"~~~( @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? String {} : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt new file mode 100644 index 0000000000000..193bdd49d9d57 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-basic.any.txt @@ -0,0 +1,28 @@ +Harness status: OK + +Found 23 tests + +23 Pass +Pass Create headers from no parameter +Pass Create headers from undefined parameter +Pass Create headers from empty object +Pass Create headers with null should throw +Pass Create headers with 1 should throw +Pass Create headers with sequence +Pass Create headers with record +Pass Create headers with existing headers +Pass Create headers with existing headers with custom iterator +Pass Check append method +Pass Check set method +Pass Check has method +Pass Check delete method +Pass Check get method +Pass Check keys method +Pass Check values method +Pass Check entries method +Pass Check Symbol.iterator method +Pass Check forEach method +Pass Iteration skips elements removed while iterating +Pass Removing elements already iterated over causes an element to be skipped during iteration +Pass Appending a value pair during iteration causes it to be reached during iteration +Pass Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-record.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-record.any.txt new file mode 100644 index 0000000000000..99705736ffe77 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-record.any.txt @@ -0,0 +1,18 @@ +Harness status: OK + +Found 13 tests + +13 Pass +Pass Passing nothing to Headers constructor +Pass Passing undefined to Headers constructor +Pass Passing null to Headers constructor +Pass Basic operation with one property +Pass Basic operation with one property and a proto +Pass Correct operation ordering with two properties +Pass Correct operation ordering with two properties one of which has an invalid name +Pass Correct operation ordering with two properties one of which has an invalid value +Pass Correct operation ordering with non-enumerable properties +Pass Correct operation ordering with undefined descriptors +Pass Correct operation ordering with repeated keys +Pass Basic operation with Symbol keys +Pass Operation with non-enumerable Symbol keys \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html new file mode 100644 index 0000000000000..130582930b82a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.html @@ -0,0 +1,15 @@ + + +Headers structure + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js new file mode 100644 index 0000000000000..ead1047645a15 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-basic.any.js @@ -0,0 +1,275 @@ +// META: title=Headers structure +// META: global=window,worker + +"use strict"; + +test(function() { + new Headers(); +}, "Create headers from no parameter"); + +test(function() { + new Headers(undefined); +}, "Create headers from undefined parameter"); + +test(function() { + new Headers({}); +}, "Create headers from empty object"); + +var parameters = [null, 1]; +parameters.forEach(function(parameter) { + test(function() { + assert_throws_js(TypeError, function() { new Headers(parameter) }); + }, "Create headers with " + parameter + " should throw"); +}); + +var headerDict = {"name1": "value1", + "name2": "value2", + "name3": "value3", + "name4": null, + "name5": undefined, + "name6": 1, + "Content-Type": "value4" +}; + +var headerSeq = []; +for (var name in headerDict) + headerSeq.push([name, headerDict[name]]); + +test(function() { + var headers = new Headers(headerSeq); + for (name in headerDict) { + assert_equals(headers.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + } + assert_equals(headers.get("length"), null, "init should be treated as a sequence, not as a dictionary"); +}, "Create headers with sequence"); + +test(function() { + var headers = new Headers(headerDict); + for (name in headerDict) { + assert_equals(headers.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + } +}, "Create headers with record"); + +test(function() { + var headers = new Headers(headerDict); + var headers2 = new Headers(headers); + for (name in headerDict) { + assert_equals(headers2.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + } +}, "Create headers with existing headers"); + +test(function() { + var headers = new Headers() + headers[Symbol.iterator] = function *() { + yield ["test", "test"] + } + var headers2 = new Headers(headers) + assert_equals(headers2.get("test"), "test") +}, "Create headers with existing headers with custom iterator"); + +test(function() { + var headers = new Headers(); + for (name in headerDict) { + headers.append(name, headerDict[name]); + assert_equals(headers.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + } +}, "Check append method"); + +test(function() { + var headers = new Headers(); + for (name in headerDict) { + headers.set(name, headerDict[name]); + assert_equals(headers.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + } +}, "Check set method"); + +test(function() { + var headers = new Headers(headerDict); + for (name in headerDict) + assert_true(headers.has(name),"headers has name " + name); + + assert_false(headers.has("nameNotInHeaders"),"headers do not have header: nameNotInHeaders"); +}, "Check has method"); + +test(function() { + var headers = new Headers(headerDict); + for (name in headerDict) { + assert_true(headers.has(name),"headers have a header: " + name); + headers.delete(name) + assert_true(!headers.has(name),"headers do not have anymore a header: " + name); + } +}, "Check delete method"); + +test(function() { + var headers = new Headers(headerDict); + for (name in headerDict) + assert_equals(headers.get(name), String(headerDict[name]), + "name: " + name + " has value: " + headerDict[name]); + + assert_equals(headers.get("nameNotInHeaders"), null, "header: nameNotInHeaders has no value"); +}, "Check get method"); + +var headerEntriesDict = {"name1": "value1", + "Name2": "value2", + "name": "value3", + "content-Type": "value4", + "Content-Typ": "value5", + "Content-Types": "value6" +}; +var sortedHeaderDict = {}; +var headerValues = []; +var sortedHeaderKeys = Object.keys(headerEntriesDict).map(function(value) { + sortedHeaderDict[value.toLowerCase()] = headerEntriesDict[value]; + headerValues.push(headerEntriesDict[value]); + return value.toLowerCase(); +}).sort(); + +var iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); +function checkIteratorProperties(iterator) { + var prototype = Object.getPrototypeOf(iterator); + assert_equals(Object.getPrototypeOf(prototype), iteratorPrototype); + + var descriptor = Object.getOwnPropertyDescriptor(prototype, "next"); + assert_true(descriptor.configurable, "configurable"); + assert_true(descriptor.enumerable, "enumerable"); + assert_true(descriptor.writable, "writable"); +} + +test(function() { + var headers = new Headers(headerEntriesDict); + var actual = headers.keys(); + checkIteratorProperties(actual); + + sortedHeaderKeys.forEach(function(key) { + const entry = actual.next(); + assert_false(entry.done); + assert_equals(entry.value, key); + }); + assert_true(actual.next().done); + assert_true(actual.next().done); + + for (const key of headers.keys()) + assert_true(sortedHeaderKeys.indexOf(key) != -1); +}, "Check keys method"); + +test(function() { + var headers = new Headers(headerEntriesDict); + var actual = headers.values(); + checkIteratorProperties(actual); + + sortedHeaderKeys.forEach(function(key) { + const entry = actual.next(); + assert_false(entry.done); + assert_equals(entry.value, sortedHeaderDict[key]); + }); + assert_true(actual.next().done); + assert_true(actual.next().done); + + for (const value of headers.values()) + assert_true(headerValues.indexOf(value) != -1); +}, "Check values method"); + +test(function() { + var headers = new Headers(headerEntriesDict); + var actual = headers.entries(); + checkIteratorProperties(actual); + + sortedHeaderKeys.forEach(function(key) { + const entry = actual.next(); + assert_false(entry.done); + assert_equals(entry.value[0], key); + assert_equals(entry.value[1], sortedHeaderDict[key]); + }); + assert_true(actual.next().done); + assert_true(actual.next().done); + + for (const entry of headers.entries()) + assert_equals(entry[1], sortedHeaderDict[entry[0]]); +}, "Check entries method"); + +test(function() { + var headers = new Headers(headerEntriesDict); + var actual = headers[Symbol.iterator](); + + sortedHeaderKeys.forEach(function(key) { + const entry = actual.next(); + assert_false(entry.done); + assert_equals(entry.value[0], key); + assert_equals(entry.value[1], sortedHeaderDict[key]); + }); + assert_true(actual.next().done); + assert_true(actual.next().done); +}, "Check Symbol.iterator method"); + +test(function() { + var headers = new Headers(headerEntriesDict); + var reference = sortedHeaderKeys[Symbol.iterator](); + headers.forEach(function(value, key, container) { + assert_equals(headers, container); + const entry = reference.next(); + assert_false(entry.done); + assert_equals(key, entry.value); + assert_equals(value, sortedHeaderDict[entry.value]); + }); + assert_true(reference.next().done); +}, "Check forEach method"); + +test(() => { + const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0"}); + const actualKeys = []; + const actualValues = []; + for (const [header, value] of headers) { + actualKeys.push(header); + actualValues.push(value); + headers.delete("foo"); + } + assert_array_equals(actualKeys, ["bar", "baz"]); + assert_array_equals(actualValues, ["0", "1"]); +}, "Iteration skips elements removed while iterating"); + +test(() => { + const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"}); + const actualKeys = []; + const actualValues = []; + for (const [header, value] of headers) { + actualKeys.push(header); + actualValues.push(value); + if (header === "baz") + headers.delete("bar"); + } + assert_array_equals(actualKeys, ["bar", "baz", "quux"]); + assert_array_equals(actualValues, ["0", "1", "3"]); +}, "Removing elements already iterated over causes an element to be skipped during iteration"); + +test(() => { + const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"}); + const actualKeys = []; + const actualValues = []; + for (const [header, value] of headers) { + actualKeys.push(header); + actualValues.push(value); + if (header === "baz") + headers.append("X-yZ", "4"); + } + assert_array_equals(actualKeys, ["bar", "baz", "foo", "quux", "x-yz"]); + assert_array_equals(actualValues, ["0", "1", "2", "3", "4"]); +}, "Appending a value pair during iteration causes it to be reached during iteration"); + +test(() => { + const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"}); + const actualKeys = []; + const actualValues = []; + for (const [header, value] of headers) { + actualKeys.push(header); + actualValues.push(value); + if (header === "baz") + headers.append("abc", "-1"); + } + assert_array_equals(actualKeys, ["bar", "baz", "baz", "foo", "quux"]); + assert_array_equals(actualValues, ["0", "1", "1", "2", "3"]); +}, "Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time"); diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.html new file mode 100644 index 0000000000000..955db2e0ce5e2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.js new file mode 100644 index 0000000000000..fa853914f4879 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-record.any.js @@ -0,0 +1,357 @@ +// META: global=window,worker + +"use strict"; + +var log = []; +function clearLog() { + log = []; +} +function addLogEntry(name, args) { + log.push([ name, ...args ]); +} + +var loggingHandler = { +}; + +setup(function() { + for (let prop of Object.getOwnPropertyNames(Reflect)) { + loggingHandler[prop] = function(...args) { + addLogEntry(prop, args); + return Reflect[prop](...args); + } + } +}); + +test(function() { + var h = new Headers(); + assert_equals([...h].length, 0); +}, "Passing nothing to Headers constructor"); + +test(function() { + var h = new Headers(undefined); + assert_equals([...h].length, 0); +}, "Passing undefined to Headers constructor"); + +test(function() { + assert_throws_js(TypeError, function() { + var h = new Headers(null); + }); +}, "Passing null to Headers constructor"); + +test(function() { + this.add_cleanup(clearLog); + var record = { a: "b" }; + var proxy = new Proxy(record, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 4); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + + // Check the results. + assert_equals([...h].length, 1); + assert_array_equals([...h.keys()], ["a"]); + assert_true(h.has("a")); + assert_equals(h.get("a"), "b"); +}, "Basic operation with one property"); + +test(function() { + this.add_cleanup(clearLog); + var recordProto = { c: "d" }; + var record = Object.create(recordProto, { a: { value: "b", enumerable: true } }); + var proxy = new Proxy(record, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 4); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + + // Check the results. + assert_equals([...h].length, 1); + assert_array_equals([...h.keys()], ["a"]); + assert_true(h.has("a")); + assert_equals(h.get("a"), "b"); +}, "Basic operation with one property and a proto"); + +test(function() { + this.add_cleanup(clearLog); + var record = { a: "b", c: "d" }; + var proxy = new Proxy(record, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 6); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]); + // Then the second [[Get]] from step 5.2. + assert_array_equals(log[5], ["get", record, "c", proxy]); + + // Check the results. + assert_equals([...h].length, 2); + assert_array_equals([...h.keys()], ["a", "c"]); + assert_true(h.has("a")); + assert_equals(h.get("a"), "b"); + assert_true(h.has("c")); + assert_equals(h.get("c"), "d"); +}, "Correct operation ordering with two properties"); + +test(function() { + this.add_cleanup(clearLog); + var record = { a: "b", "\uFFFF": "d" }; + var proxy = new Proxy(record, loggingHandler); + assert_throws_js(TypeError, function() { + var h = new Headers(proxy); + }); + + assert_equals(log.length, 5); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "\uFFFF"]); + // The second [[Get]] never happens, because we convert the invalid name to a + // ByteString first and throw. +}, "Correct operation ordering with two properties one of which has an invalid name"); + +test(function() { + this.add_cleanup(clearLog); + var record = { a: "\uFFFF", c: "d" } + var proxy = new Proxy(record, loggingHandler); + assert_throws_js(TypeError, function() { + var h = new Headers(proxy); + }); + + assert_equals(log.length, 4); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + // Nothing else after this, because converting the result of that [[Get]] to a + // ByteString throws. +}, "Correct operation ordering with two properties one of which has an invalid value"); + +test(function() { + this.add_cleanup(clearLog); + var record = {}; + Object.defineProperty(record, "a", { value: "b", enumerable: false }); + Object.defineProperty(record, "c", { value: "d", enumerable: true }); + Object.defineProperty(record, "e", { value: "f", enumerable: false }); + var proxy = new Proxy(record, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 6); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // No [[Get]] because not enumerable + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[3], ["getOwnPropertyDescriptor", record, "c"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[4], ["get", record, "c", proxy]); + // Then the third [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "e"]); + // No [[Get]] because not enumerable + + // Check the results. + assert_equals([...h].length, 1); + assert_array_equals([...h.keys()], ["c"]); + assert_true(h.has("c")); + assert_equals(h.get("c"), "d"); +}, "Correct operation ordering with non-enumerable properties"); + +test(function() { + this.add_cleanup(clearLog); + var record = {a: "b", c: "d", e: "f"}; + var lyingHandler = { + getOwnPropertyDescriptor: function(target, name) { + if (name == "a" || name == "e") { + return undefined; + } + return Reflect.getOwnPropertyDescriptor(target, name); + } + }; + var lyingProxy = new Proxy(record, lyingHandler); + var proxy = new Proxy(lyingProxy, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 6); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", lyingProxy]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", lyingProxy, "a"]); + // No [[Get]] because no descriptor + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[3], ["getOwnPropertyDescriptor", lyingProxy, "c"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[4], ["get", lyingProxy, "c", proxy]); + // Then the third [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[5], ["getOwnPropertyDescriptor", lyingProxy, "e"]); + // No [[Get]] because no descriptor + + // Check the results. + assert_equals([...h].length, 1); + assert_array_equals([...h.keys()], ["c"]); + assert_true(h.has("c")); + assert_equals(h.get("c"), "d"); +}, "Correct operation ordering with undefined descriptors"); + +test(function() { + this.add_cleanup(clearLog); + var record = {a: "b", c: "d"}; + var lyingHandler = { + ownKeys: function() { + return [ "a", "c", "a", "c" ]; + }, + }; + var lyingProxy = new Proxy(record, lyingHandler); + var proxy = new Proxy(lyingProxy, loggingHandler); + + // Returning duplicate keys from ownKeys() throws a TypeError. + assert_throws_js(TypeError, + function() { var h = new Headers(proxy); }); + + assert_equals(log.length, 2); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", lyingProxy]); +}, "Correct operation ordering with repeated keys"); + +test(function() { + this.add_cleanup(clearLog); + var record = { + a: "b", + [Symbol.toStringTag]: { + // Make sure the ToString conversion of the value happens + // after the ToString conversion of the key. + toString: function () { addLogEntry("toString", [this]); return "nope"; } + }, + c: "d" }; + var proxy = new Proxy(record, loggingHandler); + assert_throws_js(TypeError, + function() { var h = new Headers(proxy); }); + + assert_equals(log.length, 7); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]); + // Then the second [[Get]] from step 5.2. + assert_array_equals(log[5], ["get", record, "c", proxy]); + // Then the third [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[6], ["getOwnPropertyDescriptor", record, + Symbol.toStringTag]); + // Then we throw an exception converting the Symbol to a string, before we do + // the third [[Get]]. +}, "Basic operation with Symbol keys"); + +test(function() { + this.add_cleanup(clearLog); + var record = { + a: { + toString: function() { addLogEntry("toString", [this]); return "b"; } + }, + [Symbol.toStringTag]: { + toString: function () { addLogEntry("toString", [this]); return "nope"; } + }, + c: { + toString: function() { addLogEntry("toString", [this]); return "d"; } + } + }; + // Now make that Symbol-named property not enumerable. + Object.defineProperty(record, Symbol.toStringTag, { enumerable: false }); + assert_array_equals(Reflect.ownKeys(record), + ["a", "c", Symbol.toStringTag]); + + var proxy = new Proxy(record, loggingHandler); + var h = new Headers(proxy); + + assert_equals(log.length, 9); + // The first thing is the [[Get]] of Symbol.iterator to figure out whether + // we're a sequence, during overload resolution. + assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]); + // Then we have the [[OwnPropertyKeys]] from + // https://webidl.spec.whatwg.org/#es-to-record step 4. + assert_array_equals(log[1], ["ownKeys", record]); + // Then the [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]); + // Then the [[Get]] from step 5.2. + assert_array_equals(log[3], ["get", record, "a", proxy]); + // Then the ToString on the value. + assert_array_equals(log[4], ["toString", record.a]); + // Then the second [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "c"]); + // Then the second [[Get]] from step 5.2. + assert_array_equals(log[6], ["get", record, "c", proxy]); + // Then the ToString on the value. + assert_array_equals(log[7], ["toString", record.c]); + // Then the third [[GetOwnProperty]] from step 5.1. + assert_array_equals(log[8], ["getOwnPropertyDescriptor", record, + Symbol.toStringTag]); + // No [[Get]] because not enumerable. + + // Check the results. + assert_equals([...h].length, 2); + assert_array_equals([...h.keys()], ["a", "c"]); + assert_true(h.has("a")); + assert_equals(h.get("a"), "b"); + assert_true(h.has("c")); + assert_equals(h.get("c"), "d"); +}, "Operation with non-enumerable Symbol keys"); From d2acf32aaebcc1c52dd060b4fb665eef3de1ed68 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Tue, 10 Dec 2024 13:29:37 +0000 Subject: [PATCH 041/237] LibWeb: Register Wasm memory grow hook in constructor of Memory objects Previously it would only register the hook for JavaScript constructed Memory objects. This allows Ruffle to load again. --- Libraries/LibWeb/WebAssembly/Memory.cpp | 9 ++++---- .../expected/Wasm/WebAssembly-grow-hook.txt | 3 +++ .../input/Wasm/WebAssembly-grow-hook.html | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/Wasm/WebAssembly-grow-hook.txt create mode 100644 Tests/LibWeb/Text/input/Wasm/WebAssembly-grow-hook.html diff --git a/Libraries/LibWeb/WebAssembly/Memory.cpp b/Libraries/LibWeb/WebAssembly/Memory.cpp index 60e7de1d9e105..dd065023f1ec1 100644 --- a/Libraries/LibWeb/WebAssembly/Memory.cpp +++ b/Libraries/LibWeb/WebAssembly/Memory.cpp @@ -39,10 +39,6 @@ WebIDL::ExceptionOr> Memory::construct_impl(JS::Realm& realm, Me auto memory_object = realm.create(realm, *address, shared ? Shared::Yes : Shared::No); - cache.abstract_machine().store().get(*address)->successful_grow_hook = [memory_object] { - MUST(memory_object->reset_the_memory_buffer()); - }; - return memory_object; } @@ -51,6 +47,11 @@ Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address, Shared shared) , m_address(address) , m_shared(shared) { + auto& cache = Detail::get_cache(realm); + + cache.abstract_machine().store().get(address)->successful_grow_hook = [this] { + MUST(reset_the_memory_buffer()); + }; } void Memory::initialize(JS::Realm& realm) diff --git a/Tests/LibWeb/Text/expected/Wasm/WebAssembly-grow-hook.txt b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-grow-hook.txt new file mode 100644 index 0000000000000..ea8f339be68a3 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-grow-hook.txt @@ -0,0 +1,3 @@ +Size before grow: 65536 +Size after grow, before refresh: 0 +Size after grow, after refresh: 131072 diff --git a/Tests/LibWeb/Text/input/Wasm/WebAssembly-grow-hook.html b/Tests/LibWeb/Text/input/Wasm/WebAssembly-grow-hook.html new file mode 100644 index 0000000000000..2e4700c1416d3 --- /dev/null +++ b/Tests/LibWeb/Text/input/Wasm/WebAssembly-grow-hook.html @@ -0,0 +1,22 @@ + + + From a64432ec130ba1ea2d0ef97f80af4f0393dd1901 Mon Sep 17 00:00:00 2001 From: Simek Date: Mon, 9 Dec 2024 00:56:14 +0100 Subject: [PATCH 042/237] LibWeb/ARIA: Add missing `menuitemradio` widget role --- Libraries/LibWeb/ARIA/Roles.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/LibWeb/ARIA/Roles.cpp b/Libraries/LibWeb/ARIA/Roles.cpp index 823959c96ec12..aa2e182905049 100644 --- a/Libraries/LibWeb/ARIA/Roles.cpp +++ b/Libraries/LibWeb/ARIA/Roles.cpp @@ -69,6 +69,7 @@ bool is_widget_role(Role role) Role::link, Role::menuitem, Role::menuitemcheckbox, + Role::menuitemradio, Role::option, Role::progressbar, Role::radio, From 520bf6c9beedf83916837848ec6164eddac2d41c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 10 Dec 2024 11:13:40 -0500 Subject: [PATCH 043/237] LibWeb: Return a better error message for invalid byte strings We are currently returning LibJS's invalid code point message, but not formatting it with the bad value. So we get something like: Unhandled JavaScript exception: [TypeError] Invalid code point {}, must be an integer no less than 0 and no greater than 0x10FFFF So not only is the error unformatted, but it's inaccurate; in this case, the byte cannot be larger than 255. --- Libraries/LibWeb/WebIDL/AbstractOperations.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index 5476c6cc79056..0054fb51194f2 100644 --- a/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -232,9 +233,9 @@ JS::ThrowCompletionOr to_byte_string(JS::VM& vm, JS::Value value) auto x = TRY(value.to_string(vm)); // 2. If the value of any element of x is greater than 255, then throw a TypeError. - for (auto character : x.code_points()) { + for (auto [i, character] : enumerate(x.code_points())) { if (character > 0xFF) - return vm.throw_completion(JS::ErrorType::InvalidCodePoint); + return vm.throw_completion(MUST(String::formatted("Invalid byte 0x{:X} at index {}, must be an integer no less than 0 and no greater than 0xFF", character, x.code_points().byte_offset_of(i)))); } // 3. Return an IDL ByteString value whose length is the length of x, and where the value of each element is the value of the corresponding element of x. From e764df15eb5271e19507a8a345e139d4012cb620 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 21:51:19 +0000 Subject: [PATCH 044/237] LibWebView+WebContent: Inform WebContent process if browser is headless --- Libraries/LibWeb/Page/Page.h | 2 ++ Libraries/LibWeb/SVG/SVGDecodedImageData.h | 1 + Libraries/LibWebView/HelperProcess.cpp | 2 ++ Libraries/LibWebView/Options.h | 6 ++++++ Services/WebContent/PageClient.cpp | 11 +++++++++++ Services/WebContent/PageClient.h | 3 +++ Services/WebContent/main.cpp | 4 ++++ Services/WebWorker/PageHost.h | 1 + UI/Headless/Application.cpp | 1 + 9 files changed, 31 insertions(+) diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 4e0f7dfa91521..36999ca11592d 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -407,6 +407,8 @@ class PageClient : public JS::Cell { virtual DisplayListPlayerType display_list_player_type() const = 0; + virtual bool is_headless() const = 0; + protected: virtual ~PageClient() = default; }; diff --git a/Libraries/LibWeb/SVG/SVGDecodedImageData.h b/Libraries/LibWeb/SVG/SVGDecodedImageData.h index cabe0683739c8..f11fafbcfba85 100644 --- a/Libraries/LibWeb/SVG/SVGDecodedImageData.h +++ b/Libraries/LibWeb/SVG/SVGDecodedImageData.h @@ -82,6 +82,7 @@ class SVGDecodedImageData::SVGPageClient final : public PageClient { virtual bool is_ready_to_paint() const override { return true; } virtual DisplayListPlayerType display_list_player_type() const override { return m_host_page->client().display_list_player_type(); } + virtual bool is_headless() const override { return m_host_page->client().is_headless(); } private: explicit SVGPageClient(Page& host_page) diff --git a/Libraries/LibWebView/HelperProcess.cpp b/Libraries/LibWebView/HelperProcess.cpp index a40b060bd5202..a22cabba5018d 100644 --- a/Libraries/LibWebView/HelperProcess.cpp +++ b/Libraries/LibWebView/HelperProcess.cpp @@ -108,6 +108,8 @@ ErrorOr> launch_web_content_process( arguments.append("--force-fontconfig"sv); if (web_content_options.collect_garbage_on_every_allocation == WebView::CollectGarbageOnEveryAllocation::Yes) arguments.append("--collect-garbage-on-every-allocation"sv); + if (web_content_options.is_headless == WebView::IsHeadless::Yes) + arguments.append("--headless"sv); if (auto const maybe_echo_server_port = web_content_options.echo_server_port; maybe_echo_server_port.has_value()) { arguments.append("--echo-server-port"sv); diff --git a/Libraries/LibWebView/Options.h b/Libraries/LibWebView/Options.h index 75fc8b4447da4..bea15cd8ba8ca 100644 --- a/Libraries/LibWebView/Options.h +++ b/Libraries/LibWebView/Options.h @@ -113,6 +113,11 @@ enum class CollectGarbageOnEveryAllocation { Yes, }; +enum class IsHeadless { + No, + Yes, +}; + struct WebContentOptions { String command_line; String executable_path; @@ -128,6 +133,7 @@ struct WebContentOptions { EnableAutoplay enable_autoplay { EnableAutoplay::No }; CollectGarbageOnEveryAllocation collect_garbage_on_every_allocation { CollectGarbageOnEveryAllocation::No }; Optional echo_server_port {}; + IsHeadless is_headless { IsHeadless::No }; }; } diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index 05091c8a7e30c..8b12b251ec729 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -32,6 +32,7 @@ namespace WebContent { static PageClient::UseSkiaPainter s_use_skia_painter = PageClient::UseSkiaPainter::GPUBackendIfAvailable; +static bool s_is_headless { false }; GC_DEFINE_ALLOCATOR(PageClient); @@ -40,6 +41,16 @@ void PageClient::set_use_skia_painter(UseSkiaPainter use_skia_painter) s_use_skia_painter = use_skia_painter; } +bool PageClient::is_headless() const +{ + return s_is_headless; +} + +void PageClient::set_is_headless(bool is_headless) +{ + s_is_headless = is_headless; +} + GC::Ref PageClient::create(JS::VM& vm, PageHost& page_host, u64 id) { return vm.heap().allocate(page_host, id); diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index f8680b11d5be6..28d8e05270820 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -34,6 +34,9 @@ class PageClient final : public Web::PageClient { }; static void set_use_skia_painter(UseSkiaPainter); + virtual bool is_headless() const override; + static void set_is_headless(bool); + virtual bool is_ready_to_paint() const override; virtual Web::Page& page() override { return *m_page; } diff --git a/Services/WebContent/main.cpp b/Services/WebContent/main.cpp index 2a8ca2a2f109a..92eb07f919808 100644 --- a/Services/WebContent/main.cpp +++ b/Services/WebContent/main.cpp @@ -107,6 +107,7 @@ ErrorOr serenity_main(Main::Arguments arguments) bool force_cpu_painting = false; bool force_fontconfig = false; bool collect_garbage_on_every_allocation = false; + bool is_headless = false; StringView echo_server_port_string_view {}; Core::ArgsParser args_parser; @@ -127,6 +128,7 @@ ErrorOr serenity_main(Main::Arguments arguments) args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig"); args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation"); args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port"); + args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless"); args_parser.parse(arguments); @@ -152,6 +154,8 @@ ErrorOr serenity_main(Main::Arguments arguments) // Always use the CPU backend for layout tests, as the GPU backend is not deterministic WebContent::PageClient::set_use_skia_painter(force_cpu_painting ? WebContent::PageClient::UseSkiaPainter::CPUBackend : WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable); + WebContent::PageClient::set_is_headless(is_headless); + if (enable_http_cache) { Web::Fetch::Fetching::g_http_cache_enabled = true; } diff --git a/Services/WebWorker/PageHost.h b/Services/WebWorker/PageHost.h index dc2ea373b72ad..86a008e1aad12 100644 --- a/Services/WebWorker/PageHost.h +++ b/Services/WebWorker/PageHost.h @@ -37,6 +37,7 @@ class PageHost final : public Web::PageClient { virtual void request_file(Web::FileRequest) override; virtual bool is_ready_to_paint() const override { return true; } virtual Web::DisplayListPlayerType display_list_player_type() const override { VERIFY_NOT_REACHED(); } + virtual bool is_headless() const override { VERIFY_NOT_REACHED(); } private: explicit PageHost(ConnectionFromClient&); diff --git a/UI/Headless/Application.cpp b/UI/Headless/Application.cpp index 35f5ab0db6e18..cd1103825d29f 100644 --- a/UI/Headless/Application.cpp +++ b/UI/Headless/Application.cpp @@ -85,6 +85,7 @@ void Application::create_platform_options(WebView::ChromeOptions& chrome_options } web_content_options.is_layout_test_mode = is_layout_test_mode ? WebView::IsLayoutTestMode::Yes : WebView::IsLayoutTestMode::No; + web_content_options.is_headless = WebView::IsHeadless::Yes; } ErrorOr Application::launch_test_fixtures() From a44b18236cc76f3f258926a4e16692109bb77dee Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 21:53:31 +0000 Subject: [PATCH 045/237] LibWeb: Add an `Internals.headless` attribute This returns true if the browser is running in headless mode. --- Libraries/LibWeb/Internals/Internals.cpp | 5 +++++ Libraries/LibWeb/Internals/Internals.h | 2 ++ Libraries/LibWeb/Internals/Internals.idl | 2 ++ Tests/LibWeb/Text/expected/Internals/headless.txt | 1 + Tests/LibWeb/Text/input/Internals/headless.html | 7 +++++++ 5 files changed, 17 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/Internals/headless.txt create mode 100644 Tests/LibWeb/Text/input/Internals/headless.html diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index fb2914923c579..837740cdde060 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -239,4 +239,9 @@ void Internals::set_echo_server_port(u16 const port) s_echo_server_port = port; } +bool Internals::headless() +{ + return internals_page().client().is_headless(); +} + } diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index 56dc5e2a01538..ba870af6b326b 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -55,6 +55,8 @@ class Internals final : public Bindings::PlatformObject { static u16 get_echo_server_port(); static void set_echo_server_port(u16 port); + bool headless(); + private: explicit Internals(JS::Realm&); virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index 64a2994a655ea..04776e3bdad2f 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -42,4 +42,6 @@ interface Internals { DOMString getComputedRole(Element element); DOMString getComputedLabel(Element element); unsigned short getEchoServerPort(); + + readonly attribute boolean headless; }; diff --git a/Tests/LibWeb/Text/expected/Internals/headless.txt b/Tests/LibWeb/Text/expected/Internals/headless.txt new file mode 100644 index 0000000000000..30e0d223cd7cc --- /dev/null +++ b/Tests/LibWeb/Text/expected/Internals/headless.txt @@ -0,0 +1 @@ +Browser is running headlessly: true diff --git a/Tests/LibWeb/Text/input/Internals/headless.html b/Tests/LibWeb/Text/input/Internals/headless.html new file mode 100644 index 0000000000000..ab1cf4f36733f --- /dev/null +++ b/Tests/LibWeb/Text/input/Internals/headless.html @@ -0,0 +1,7 @@ + + + From ae5305981691d196a2ae333a156d0e4aaf7fa12d Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 21:58:16 +0000 Subject: [PATCH 046/237] Tests: Don't display imported WPT test output if browser is headless Previously, imported WPT tests didn't display any output if the internals object was exposed. This change adds the condition that the browser must also be running headlessly for test output to not be displayed. --- Tests/LibWeb/Text/input/wpt-import/resources/testharness.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js b/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js index 9c536f888015a..d56e05112d07b 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js @@ -9,7 +9,7 @@ // default timeout is 10 seconds, test can override if needed var settings = { // Assume we are running tests if the internals object is exposed. - output:!window.hasOwnProperty("internals"), + output: !(window.internals && window.internals.headless), harness_timeout:{ "normal":150000, // NOTE: Overridden for Ladybird due to slow GCC CI "long":300000 // NOTE: Overridden for Ladybird due to slow GCC CI @@ -4549,7 +4549,7 @@ AssertionError.prototype = Object.create(Error.prototype); const get_stack = function() { - if (window.internals) { + if (window.internals && window.internals.headless) { return "(Stack traces disabled in Ladybird test mode)"; } From 495006ddb58de490a685f8ce2e3312c91c8316ef Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Tue, 10 Dec 2024 16:16:56 +0100 Subject: [PATCH 047/237] LibWeb: Implement document.execCommand('insertLinebreak') --- Libraries/LibWeb/Editing/Commands.cpp | 77 +++++++++++++++++++ Libraries/LibWeb/Editing/Commands.h | 1 + .../Editing/execCommand-insertLinebreak.txt | 9 +++ .../Editing/execCommand-insertLinebreak.html | 40 ++++++++++ 4 files changed, 127 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt create mode 100644 Tests/LibWeb/Text/input/Editing/execCommand-insertLinebreak.html diff --git a/Libraries/LibWeb/Editing/Commands.cpp b/Libraries/LibWeb/Editing/Commands.cpp index 6c82757209c12..2cdb6d32dedff 100644 --- a/Libraries/LibWeb/Editing/Commands.cpp +++ b/Libraries/LibWeb/Editing/Commands.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace Web::Editing { @@ -374,6 +375,81 @@ bool command_delete_action(DOM::Document& document, String const&) return true; } +// https://w3c.github.io/editing/docs/execCommand/#the-insertlinebreak-command +bool command_insert_linebreak_action(DOM::Document& document, String const&) +{ + // 1. Delete the selection, with strip wrappers false. + auto& selection = *document.get_selection(); + delete_the_selection(selection, true, false); + + // 2. If the active range's start node is neither editable nor an editing host, return true. + auto& active_range = *selection.range(); + auto start_node = active_range.start_container(); + if (!start_node->is_editable_or_editing_host()) + return true; + + // 3. If the active range's start node is an Element, and "br" is not an allowed child of it, return true. + if (is(*start_node) && !is_allowed_child_of_node(HTML::TagNames::br, start_node)) + return true; + + // 4. If the active range's start node is not an Element, and "br" is not an allowed child of the active range's + // start node's parent, return true. + if (!is(*start_node) && start_node->parent() && !is_allowed_child_of_node(HTML::TagNames::br, GC::Ref { *start_node->parent() })) + return true; + + // 5. If the active range's start node is a Text node and its start offset is zero, call collapse() on the context + // object's selection, with first argument equal to the active range's start node's parent and second argument + // equal to the active range's start node's index. + if (is(*start_node) && active_range.start_offset() == 0) + MUST(selection.collapse(start_node->parent(), start_node->index())); + + // 6. If the active range's start node is a Text node and its start offset is the length of its start node, call + // collapse() on the context object's selection, with first argument equal to the active range's start node's + // parent and second argument equal to one plus the active range's start node's index. + if (is(*start_node) && active_range.start_offset() == start_node->length()) + MUST(selection.collapse(start_node->parent(), start_node->index() + 1)); + + // AD-HOC: If the active range's start node is a Text node and its resolved value for "white-space" is one of "pre", + // "pre-line" or "pre-wrap": + // * Insert a newline (\n) character at the active range's start offset; + // * Collapse the selection with active range's start node as the first argument and one plus active range's + // start offset as the second argument + // * Insert another newline (\n) character if the active range's start offset is equal to the length of the + // active range's start node. + // * Return true. + if (is(*start_node) && start_node->layout_node()) { + auto& text_node = static_cast(*start_node); + auto white_space = text_node.layout_node()->computed_values().white_space(); + if (first_is_one_of(white_space, CSS::WhiteSpace::Pre, CSS::WhiteSpace::PreLine, CSS::WhiteSpace::PreWrap)) { + MUST(text_node.insert_data(active_range.start_offset(), "\n"_string)); + MUST(selection.collapse(start_node, active_range.start_offset() + 1)); + if (selection.range()->start_offset() == start_node->length()) + MUST(text_node.insert_data(active_range.start_offset(), "\n"_string)); + return true; + } + } + + // 7. Let br be the result of calling createElement("br") on the context object. + auto br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); + + // 8. Call insertNode(br) on the active range. + MUST(active_range.insert_node(br)); + + // 9. Call collapse() on the context object's selection, with br's parent as the first argument and one plus br's + // index as the second argument. + MUST(selection.collapse(br->parent(), br->index() + 1)); + + // 10. If br is a collapsed line break, call createElement("br") on the context object and let extra br be the + // result, then call insertNode(extra br) on the active range. + if (is_collapsed_line_break(br)) { + auto extra_br = MUST(DOM::create_element(document, HTML::TagNames::br, Namespace::HTML)); + MUST(active_range.insert_node(extra_br)); + } + + // 11. Return true. + return true; +} + // https://w3c.github.io/editing/docs/execCommand/#the-insertparagraph-command bool command_insert_paragraph_action(DOM::Document& document, String const&) { @@ -705,6 +781,7 @@ bool command_style_with_css_state(DOM::Document const& document) static Array const commands { CommandDefinition { CommandNames::delete_, command_delete_action, {}, {}, {} }, CommandDefinition { CommandNames::defaultParagraphSeparator, command_default_paragraph_separator_action, {}, {}, command_default_paragraph_separator_value }, + CommandDefinition { CommandNames::insertLineBreak, command_insert_linebreak_action, {}, {}, {} }, CommandDefinition { CommandNames::insertParagraph, command_insert_paragraph_action, {}, {}, {} }, CommandDefinition { CommandNames::styleWithCSS, command_style_with_css_action, {}, command_style_with_css_state, {} }, }; diff --git a/Libraries/LibWeb/Editing/Commands.h b/Libraries/LibWeb/Editing/Commands.h index 6eb3f8385d3fe..9dbe90d5ee65d 100644 --- a/Libraries/LibWeb/Editing/Commands.h +++ b/Libraries/LibWeb/Editing/Commands.h @@ -24,6 +24,7 @@ Optional find_command_definition(FlyString const&); bool command_default_paragraph_separator_action(DOM::Document&, String const&); String command_default_paragraph_separator_value(DOM::Document const&); bool command_delete_action(DOM::Document&, String const&); +bool command_insert_linebreak_action(DOM::Document&, String const&); bool command_insert_paragraph_action(DOM::Document&, String const&); bool command_style_with_css_action(DOM::Document&, String const&); bool command_style_with_css_state(DOM::Document const&); diff --git a/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt b/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt new file mode 100644 index 0000000000000..1ec240902d4e7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Editing/execCommand-insertLinebreak.txt @@ -0,0 +1,9 @@ +Before: "foobar" +After: "foo
bar" +Before: "

foobar

" +After: "

foo +bar

" +Before: "

foobar

" +After: "

foobar + +

" diff --git a/Tests/LibWeb/Text/input/Editing/execCommand-insertLinebreak.html b/Tests/LibWeb/Text/input/Editing/execCommand-insertLinebreak.html new file mode 100644 index 0000000000000..81bc658b50c3e --- /dev/null +++ b/Tests/LibWeb/Text/input/Editing/execCommand-insertLinebreak.html @@ -0,0 +1,40 @@ + +
foobar
+

foobar

+

foobar

+ From 0b2fe008a3e771526e82de31b4af77472968d24b Mon Sep 17 00:00:00 2001 From: mkljczk Date: Tue, 10 Dec 2024 19:12:58 +0100 Subject: [PATCH 048/237] Docs: Add perl-lib to build prerequisites for Fedora --- Documentation/BuildInstructionsLadybird.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/BuildInstructionsLadybird.md b/Documentation/BuildInstructionsLadybird.md index e6a7937f430f2..902c6a04b8c49 100644 --- a/Documentation/BuildInstructionsLadybird.md +++ b/Documentation/BuildInstructionsLadybird.md @@ -84,7 +84,7 @@ sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl l ### Fedora or derivatives: ``` -sudo dnf install autoconf-archive automake ccache cmake curl liberation-sans-fonts libglvnd-devel nasm ninja-build perl-FindBin perl-IPC-Cmd qt6-qtbase-devel qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static +sudo dnf install autoconf-archive automake ccache cmake curl liberation-sans-fonts libglvnd-devel nasm ninja-build perl-FindBin perl-IPC-Cmd perl-lib qt6-qtbase-devel qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static ``` ### openSUSE: From fe891727dc264e23d34b877c8b6da4df80b07bc0 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 6 Dec 2024 16:24:08 -0500 Subject: [PATCH 049/237] LibWeb: Use correct URL parsing methods throughout LibWeb There are essentially 3 URL parsing AOs defined by the spec: 1. Parse a URL 2. Encoding parse a URL 3. Encoding parse a URL and serialize the result Further, these are replicated between the Document and the ESO. This patch defines these methods in accordance with the spec and updates existing users to invoke the correct method. In places where the correct method is ambiguous, we use the encoding parser to preserve existing ad- hoc behavior. --- Libraries/LibWeb/CSS/SelectorEngine.cpp | 2 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 2 +- Libraries/LibWeb/DOM/Document.cpp | 33 +++++++++++++- Libraries/LibWeb/DOM/Document.h | 2 + Libraries/LibWeb/HTML/EventSource.cpp | 2 +- Libraries/LibWeb/HTML/HTMLBodyElement.cpp | 3 +- Libraries/LibWeb/HTML/HTMLFormElement.cpp | 2 +- .../LibWeb/HTML/HTMLHyperlinkElementUtils.cpp | 17 +++---- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLLinkElement.cpp | 10 +++-- Libraries/LibWeb/HTML/HTMLObjectElement.cpp | 4 +- .../LibWeb/HTML/HTMLTableCellElement.cpp | 3 +- Libraries/LibWeb/HTML/HTMLTableElement.cpp | 3 +- Libraries/LibWeb/HTML/HTMLTableRowElement.cpp | 3 +- .../LibWeb/HTML/HTMLTableSectionElement.cpp | 2 +- Libraries/LibWeb/HTML/Location.cpp | 9 ++-- .../LibWeb/HTML/Scripting/Environments.cpp | 45 +++++++++++++++---- .../LibWeb/HTML/Scripting/Environments.h | 2 + Libraries/LibWeb/HTML/Window.cpp | 4 +- Libraries/LibWeb/HTML/Worker.cpp | 2 +- Libraries/LibWeb/HTML/WorkerGlobalScope.cpp | 2 +- Libraries/LibWeb/Page/EventHandler.cpp | 8 ++-- Libraries/LibWeb/SVG/SVGGradientElement.cpp | 2 +- .../BindingsGenerator/IDLGenerators.cpp | 8 ++-- Services/WebContent/ConnectionFromClient.cpp | 2 +- 25 files changed, 118 insertions(+), 56 deletions(-) diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index 420018e19e775..eef6bfc313f62 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -464,7 +464,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla if (!matches_link_pseudo_class(element)) return false; auto document_url = element.document().url(); - URL::URL target_url = element.document().parse_url(element.attribute(HTML::AttributeNames::href).value_or({})); + URL::URL target_url = element.document().encoding_parse_url(element.attribute(HTML::AttributeNames::href).value_or({})); if (target_url.fragment().has_value()) return document_url.equals(target_url, URL::ExcludeFragment::No); return document_url.equals(target_url, URL::ExcludeFragment::Yes); diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index f00126e38f8ed..3683c181136b2 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -2825,7 +2825,7 @@ Optional StyleComputer::load_font_face(ParsedFontFace const& font_f for (auto const& source : font_face.sources()) { // FIXME: These should be loaded relative to the stylesheet URL instead of the document URL. if (source.local_or_url.has()) - urls.append(m_document->parse_url(source.local_or_url.get().to_string())); + urls.append(m_document->encoding_parse_url(source.local_or_url.get().to_string())); // FIXME: Handle local() } diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index b852554b78b31..6cbaa010782d7 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1081,7 +1081,38 @@ URL::URL Document::parse_url(StringView url) const auto base_url = this->base_url(); // 2. Return the result of applying the URL parser to url, with baseURL. - return DOMURL::parse(url, base_url, Optional { m_encoding }); + return DOMURL::parse(url, base_url); +} + +// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-a-url +URL::URL Document::encoding_parse_url(StringView url) const +{ + // 1. Let encoding be UTF-8. + // 2. If environment is a Document object, then set encoding to environment's character encoding. + auto encoding = encoding_or_default(); + + // 3. Otherwise, if environment's relevant global object is a Window object, set encoding to environment's relevant + // global object's associated Document's character encoding. + + // 4. Let baseURL be environment's base URL, if environment is a Document object; otherwise environment's API base URL. + auto base_url = this->base_url(); + + // 5. Return the result of applying the URL parser to url, with baseURL and encoding. + return DOMURL::parse(url, base_url, encoding); +} + +// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-and-serializing-a-url +Optional Document::encoding_parse_and_serialize_url(StringView url) const +{ + // 1. Let url be the result of encoding-parsing a URL given url, relative to environment. + auto parsed_url = encoding_parse_url(url); + + // 2. If url is failure, then return failure. + if (!parsed_url.is_valid()) + return {}; + + // 3. Return the result of applying the URL serializer to url. + return parsed_url.serialize(); } void Document::set_needs_layout() diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index f8cd52a6e88ba..c5d6ad140fcb9 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -157,6 +157,8 @@ class Document void set_opener_policy(HTML::OpenerPolicy policy) { m_opener_policy = move(policy); } URL::URL parse_url(StringView) const; + URL::URL encoding_parse_url(StringView) const; + Optional encoding_parse_and_serialize_url(StringView) const; CSS::StyleComputer& style_computer() { return *m_style_computer; } const CSS::StyleComputer& style_computer() const { return *m_style_computer; } diff --git a/Libraries/LibWeb/HTML/EventSource.cpp b/Libraries/LibWeb/HTML/EventSource.cpp index 79ffec187c52a..1bfaef1449931 100644 --- a/Libraries/LibWeb/HTML/EventSource.cpp +++ b/Libraries/LibWeb/HTML/EventSource.cpp @@ -43,7 +43,7 @@ WebIDL::ExceptionOr> EventSource::construct_impl(JS::Realm& auto& settings = relevant_settings_object(event_source); // 3. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings. - auto url_record = settings.parse_url(url); + auto url_record = settings.encoding_parse_url(url); // 4. If urlRecord is failure, then throw a "SyntaxError" DOMException. if (!url_record.is_valid()) diff --git a/Libraries/LibWeb/HTML/HTMLBodyElement.cpp b/Libraries/LibWeb/HTML/HTMLBodyElement.cpp index 0326afb764e66..d4d75cff7d0d4 100644 --- a/Libraries/LibWeb/HTML/HTMLBodyElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLBodyElement.cpp @@ -113,7 +113,8 @@ void HTMLBodyElement::attribute_changed(FlyString const& name, Optional if (color.has_value()) document().set_visited_link_color(color.value()); } else if (name.equals_ignoring_ascii_case("background"sv)) { - m_background_style_value = CSS::ImageStyleValue::create(document().parse_url(value.value_or(String {}))); + // https://html.spec.whatwg.org/multipage/rendering.html#the-page:attr-background + m_background_style_value = CSS::ImageStyleValue::create(document().encoding_parse_url(value.value_or(String {}))); m_background_style_value->on_animate = [this] { if (paintable()) { paintable()->set_needs_display(); diff --git a/Libraries/LibWeb/HTML/HTMLFormElement.cpp b/Libraries/LibWeb/HTML/HTMLFormElement.cpp index c397482a86ac1..3849c18e24ac5 100644 --- a/Libraries/LibWeb/HTML/HTMLFormElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLFormElement.cpp @@ -222,7 +222,7 @@ WebIDL::ExceptionOr HTMLFormElement::submit_form(GC::Ref subm // 14. Parse a URL given action, relative to the submitter element's node document. If this fails, return. // 15. Let parsed action be the resulting URL record. - auto parsed_action = document().parse_url(action); + auto parsed_action = submitter->document().parse_url(action); if (!parsed_action.is_valid()) { dbgln("Failed to submit form: Invalid URL: {}", action); return {}; diff --git a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp index cb496c7340859..2e823aa2b36d0 100644 --- a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp +++ b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp @@ -486,22 +486,15 @@ void HTMLHyperlinkElementUtils::follow_the_hyperlink(Optional hyperlink_ // 8. Let urlString be the result of encoding-parsing-and-serializing a URL given subject's href attribute value, // relative to subject's node document. - auto url = hyperlink_element_utils_document().parse_url(href()); + auto url_string = hyperlink_element_utils_document().encoding_parse_and_serialize_url(href()); // 9. If urlString is failure, then return. - if (!url.is_valid()) + if (!url_string.has_value()) return; - auto url_string = url.to_string(); - // 10. If hyperlinkSuffix is non-null, then append it to urlString. - if (hyperlink_suffix.has_value()) { - StringBuilder url_builder; - url_builder.append(url_string); - url_builder.append(*hyperlink_suffix); - - url_string = MUST(url_builder.to_string()); - } + if (hyperlink_suffix.has_value()) + url_string = MUST(String::formatted("{}{}", *url_string, *hyperlink_suffix)); // 11. Let referrerPolicy be the current state of subject's referrerpolicy content attribute. auto referrer_policy = ReferrerPolicy::from_string(hyperlink_element_utils_referrerpolicy().value_or({})).value_or(ReferrerPolicy::ReferrerPolicy::EmptyString); @@ -509,7 +502,7 @@ void HTMLHyperlinkElementUtils::follow_the_hyperlink(Optional hyperlink_ // FIXME: 12. If subject's link types includes the noreferrer keyword, then set referrerPolicy to "no-referrer". // 13. Navigate targetNavigable to urlString using subject's node document, with referrerPolicy set to referrerPolicy and userInvolvement set to userInvolvement. - MUST(target_navigable->navigate({ .url = url_string, .source_document = hyperlink_element_utils_document(), .referrer_policy = referrer_policy, .user_involvement = user_involvement })); + MUST(target_navigable->navigate({ .url = *url_string, .source_document = hyperlink_element_utils_document(), .referrer_policy = referrer_policy, .user_involvement = user_involvement })); } } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index c4a6d14a9bd98..c8e9034664eb9 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1319,7 +1319,7 @@ WebIDL::ExceptionOr HTMLInputElement::handle_src_attribute(String const& v // 1. Let url be the result of encoding-parsing a URL given the src attribute's value, relative to the element's // node document. - auto url = document().parse_url(value); + auto url = document().encoding_parse_url(value); // 2. If url is failure, then return. if (!url.is_valid()) diff --git a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index 852155bd71c01..f4aa0b7c48ca7 100644 --- a/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -78,14 +78,14 @@ void HTMLLinkElement::inserted() if (m_relationship & Relationship::Preload) { // FIXME: Respect the "as" attribute. LoadRequest request; - request.set_url(document().parse_url(get_attribute_value(HTML::AttributeNames::href))); + request.set_url(document().encoding_parse_url(get_attribute_value(HTML::AttributeNames::href))); set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); } else if (m_relationship & Relationship::DNSPrefetch) { - ResourceLoader::the().prefetch_dns(document().parse_url(get_attribute_value(HTML::AttributeNames::href))); + ResourceLoader::the().prefetch_dns(document().encoding_parse_url(get_attribute_value(HTML::AttributeNames::href))); } else if (m_relationship & Relationship::Preconnect) { - ResourceLoader::the().preconnect(document().parse_url(get_attribute_value(HTML::AttributeNames::href))); + ResourceLoader::the().preconnect(document().encoding_parse_url(get_attribute_value(HTML::AttributeNames::href))); } else if (m_relationship & Relationship::Icon) { - auto favicon_url = document().parse_url(href()); + auto favicon_url = document().encoding_parse_url(href()); auto favicon_request = LoadRequest::create_for_url_on_page(favicon_url, &document().page()); set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, favicon_request)); } @@ -261,6 +261,8 @@ GC::Ptr HTMLLinkElement::create_link_request(HTM // FIXME: 2. If options's destination is null, then return null. // 3. Let url be the result of encoding-parsing a URL given options's href, relative to options's base URL. + // FIXME: Spec issue: We should be parsing this URL relative to a document or environment settings object. + // https://github.com/whatwg/html/issues/9715 auto url = options.base_url.complete_url(options.href); // 4. If url is failure, then return null. diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp index 170c5a2a6022b..64681cf0a8563 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp @@ -137,7 +137,7 @@ String HTMLObjectElement::data() const if (!data.has_value()) return {}; - return document().parse_url(*data).to_string(); + return document().encoding_parse_url(*data).to_string(); } GC::Ptr HTMLObjectElement::create_layout_node(CSS::StyleProperties style) @@ -401,7 +401,7 @@ void HTMLObjectElement::load_image() { // NOTE: This currently reloads the image instead of reusing the resource we've already downloaded. auto data = get_attribute_value(HTML::AttributeNames::data); - auto url = document().parse_url(data); + auto url = document().encoding_parse_url(data); m_resource_request = HTML::SharedResourceRequest::get_or_create(realm(), document().page(), url); m_resource_request->add_callbacks( [this] { diff --git a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp index 1188ec0a53b2f..a482a628d7007 100644 --- a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp @@ -74,7 +74,8 @@ void HTMLTableCellElement::apply_presentational_hints(CSS::StyleProperties& styl style.set_property(CSS::PropertyID::Height, parsed_value.release_nonnull()); return; } else if (name == HTML::AttributeNames::background) { - if (auto parsed_value = document().parse_url(value); parsed_value.is_valid()) + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url + if (auto parsed_value = document().encoding_parse_url(value); parsed_value.is_valid()) style.set_property(CSS::PropertyID::BackgroundImage, CSS::ImageStyleValue::create(parsed_value)); return; } diff --git a/Libraries/LibWeb/HTML/HTMLTableElement.cpp b/Libraries/LibWeb/HTML/HTMLTableElement.cpp index 0603164e42ffb..cd391d7a7c00b 100644 --- a/Libraries/LibWeb/HTML/HTMLTableElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableElement.cpp @@ -74,7 +74,8 @@ void HTMLTableElement::apply_presentational_hints(CSS::StyleProperties& style) c return; } if (name == HTML::AttributeNames::background) { - if (auto parsed_value = document().parse_url(value); parsed_value.is_valid()) + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url + if (auto parsed_value = document().encoding_parse_url(value); parsed_value.is_valid()) style.set_property(CSS::PropertyID::BackgroundImage, CSS::ImageStyleValue::create(parsed_value)); return; } diff --git a/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp b/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp index 6e71e30bc3d99..4c4d37882a96e 100644 --- a/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableRowElement.cpp @@ -49,7 +49,8 @@ void HTMLTableRowElement::apply_presentational_hints(CSS::StyleProperties& style if (color.has_value()) style.set_property(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value())); } else if (name == HTML::AttributeNames::background) { - if (auto parsed_value = document().parse_url(value); parsed_value.is_valid()) + // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url + if (auto parsed_value = document().encoding_parse_url(value); parsed_value.is_valid()) style.set_property(CSS::PropertyID::BackgroundImage, CSS::ImageStyleValue::create(parsed_value)); } else if (name == HTML::AttributeNames::height) { if (auto parsed_value = parse_dimension_value(value)) diff --git a/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp b/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp index 31e4fc91a8a01..21ed29ea99509 100644 --- a/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableSectionElement.cpp @@ -105,7 +105,7 @@ void HTMLTableSectionElement::apply_presentational_hints(CSS::StyleProperties& s for_each_attribute([&](auto& name, auto& value) { // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url if (name == HTML::AttributeNames::background) { - if (auto parsed_value = document().parse_url(value); parsed_value.is_valid()) + if (auto parsed_value = document().encoding_parse_url(value); parsed_value.is_valid()) style.set_property(CSS::PropertyID::BackgroundImage, CSS::ImageStyleValue::create(parsed_value)); } // https://html.spec.whatwg.org/multipage/rendering.html#tables-2:rules-for-parsing-a-legacy-colour-value diff --git a/Libraries/LibWeb/HTML/Location.cpp b/Libraries/LibWeb/HTML/Location.cpp index 9ca6031f74aa2..ab04b03e3be89 100644 --- a/Libraries/LibWeb/HTML/Location.cpp +++ b/Libraries/LibWeb/HTML/Location.cpp @@ -108,22 +108,21 @@ WebIDL::ExceptionOr Location::href() const WebIDL::ExceptionOr Location::set_href(String const& new_href) { auto& realm = this->realm(); - auto& window = verify_cast(HTML::current_principal_global_object()); // 1. If this's relevant Document is null, then return. auto const relevant_document = this->relevant_document(); if (!relevant_document) return {}; - // FIXME: 2. Let url be the result of encoding-parsing a URL given the given value, relative to the entry settings object. - auto href_url = window.associated_document().parse_url(new_href.to_byte_string()); + // 2. Let url be the result of encoding-parsing a URL given the given value, relative to the entry settings object. + auto url = entry_settings_object().encoding_parse_url(new_href.to_byte_string()); // 3. If url is failure, then throw a "SyntaxError" DOMException. - if (!href_url.is_valid()) + if (!url.is_valid()) return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid URL '{}'", new_href))); // 4. Location-object navigate this to url. - TRY(navigate(href_url)); + TRY(navigate(url)); return {}; } diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Libraries/LibWeb/HTML/Scripting/Environments.cpp index 1379f1d60708a..217711cc0b71b 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -201,17 +202,45 @@ void prepare_to_run_callback(JS::Realm& realm) // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#parse-a-url URL::URL EnvironmentSettingsObject::parse_url(StringView url) { - // 1. Let encoding be document's character encoding, if document was given, and environment settings object's API URL character encoding otherwise. - // FIXME: Pass in environment settings object's API URL character encoding. + // 1. Let baseURL be environment's base URL, if environment is a Document object; otherwise environment's API base URL. + auto base_url = api_base_url(); + + // 2. Return the result of applying the URL parser to url, with baseURL. + return DOMURL::parse(url, base_url); +} + +// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-a-url +URL::URL EnvironmentSettingsObject::encoding_parse_url(StringView url) +{ + // 1. Let encoding be UTF-8. + auto encoding = "UTF-8"_string; + + // 2. If environment is a Document object, then set encoding to environment's character encoding. - // 2. Let baseURL be document's base URL, if document was given, and environment settings object's API base URL otherwise. + // 3. Otherwise, if environment's relevant global object is a Window object, set encoding to environment's relevant + // global object's associated Document's character encoding. + if (is(global_object())) + encoding = static_cast(global_object()).associated_document().encoding_or_default(); + + // 4. Let baseURL be environment's base URL, if environment is a Document object; otherwise environment's API base URL. auto base_url = api_base_url(); - // 3. Let urlRecord be the result of applying the URL parser to url, with baseURL and encoding. - // 4. If urlRecord is failure, then return failure. - // 5. Let urlString be the result of applying the URL serializer to urlRecord. - // 6. Return urlString as the resulting URL string and urlRecord as the resulting URL record. - return base_url.complete_url(url); + // 5. Return the result of applying the URL parser to url, with baseURL and encoding. + return DOMURL::parse(url, base_url, encoding); +} + +// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#encoding-parsing-and-serializing-a-url +Optional EnvironmentSettingsObject::encoding_parse_and_serialize_url(StringView url) +{ + // 1. Let url be the result of encoding-parsing a URL given url, relative to environment. + auto parsed_url = encoding_parse_url(url); + + // 2. If url is failure, then return failure. + if (!parsed_url.is_valid()) + return {}; + + // 3. Return the result of applying the URL serializer to url. + return parsed_url.serialize(); } // https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-a-callback diff --git a/Libraries/LibWeb/HTML/Scripting/Environments.h b/Libraries/LibWeb/HTML/Scripting/Environments.h index 118cbfeba4964..d7adc8d6c74e1 100644 --- a/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -88,6 +88,8 @@ struct EnvironmentSettingsObject : public Environment { virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() const = 0; URL::URL parse_url(StringView); + URL::URL encoding_parse_url(StringView); + Optional encoding_parse_and_serialize_url(StringView); JS::Realm& realm(); JS::Object& global_object(); diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 4168c3f46253c..dd0774ee5e024 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -197,8 +197,8 @@ WebIDL::ExceptionOr Window::window_open_steps_internal(Str // 4. If url is not the empty string, then: if (!url.is_empty()) { - // FIXME: 1. Set urlRecord to the result of encoding-parsing a URL given url, relative to sourceDocument. - url_record = entry_settings_object().parse_url(url); + // 1. Set urlRecord to the result of encoding-parsing a URL given url, relative to sourceDocument. + url_record = source_document.encoding_parse_url(url); // 2. If urlRecord is failure, then throw a "SyntaxError" DOMException. if (!url_record->is_valid()) diff --git a/Libraries/LibWeb/HTML/Worker.cpp b/Libraries/LibWeb/HTML/Worker.cpp index 5a7895bb05de8..390f9385f0ce3 100644 --- a/Libraries/LibWeb/HTML/Worker.cpp +++ b/Libraries/LibWeb/HTML/Worker.cpp @@ -60,7 +60,7 @@ WebIDL::ExceptionOr> Worker::create(String const& script_url, Wo auto& outside_settings = current_principal_settings_object(); // 3. Parse the scriptURL argument relative to outside settings. - auto url = document.parse_url(script_url); + auto url = outside_settings.parse_url(script_url); // 4. If this fails, throw a "SyntaxError" DOMException. if (!url.is_valid()) { diff --git a/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp b/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp index db3c8883e5b42..2d85e72ed7bfc 100644 --- a/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp +++ b/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp @@ -103,7 +103,7 @@ WebIDL::ExceptionOr WorkerGlobalScope::import_scripts(Vector const // 5. For each url of urls: for (auto const& url : urls) { // 1. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings object. - auto url_record = settings_object.parse_url(url); + auto url_record = settings_object.encoding_parse_url(url); // 2. If urlRecord is failure, then throw a "SyntaxError" DOMException. if (!url_record.is_valid()) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index 71a3282c0bba4..961c882327156 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -331,7 +331,7 @@ EventResult EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPix if (GC::Ptr link = node->enclosing_link_element()) { GC::Ref document = *m_navigable->active_document(); auto href = link->href(); - auto url = document->parse_url(href); + auto url = document->encoding_parse_url(href); if (button == UIEvents::MouseButton::Primary && (modifiers & UIEvents::Mod_PlatformCtrl) != 0) { m_navigable->page().client().page_did_click_link(url, link->target().to_byte_string(), modifiers); @@ -343,13 +343,13 @@ EventResult EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPix } else if (button == UIEvents::MouseButton::Secondary) { if (is(*node)) { auto& image_element = verify_cast(*node); - auto image_url = image_element.document().parse_url(image_element.src()); + auto image_url = image_element.document().encoding_parse_url(image_element.src()); m_navigable->page().client().page_did_request_image_context_menu(viewport_position, image_url, "", modifiers, image_element.immutable_bitmap()->bitmap()); } else if (is(*node)) { auto& media_element = verify_cast(*node); Page::MediaContextMenu menu { - .media_url = media_element.document().parse_url(media_element.current_src()), + .media_url = media_element.document().encoding_parse_url(media_element.current_src()), .is_video = is(*node), .is_playing = media_element.potentially_playing(), .is_muted = media_element.muted(), @@ -636,7 +636,7 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP if (is_hovering_link) { page.set_is_hovering_link(true); - page.client().page_did_hover_link(document.parse_url(hovered_link_element->href())); + page.client().page_did_hover_link(document.encoding_parse_url(hovered_link_element->href())); } else if (page.is_hovering_link()) { page.set_is_hovering_link(false); page.client().page_did_unhover_link(); diff --git a/Libraries/LibWeb/SVG/SVGGradientElement.cpp b/Libraries/LibWeb/SVG/SVGGradientElement.cpp index e090a3feae401..2fd2441b929c1 100644 --- a/Libraries/LibWeb/SVG/SVGGradientElement.cpp +++ b/Libraries/LibWeb/SVG/SVGGradientElement.cpp @@ -128,7 +128,7 @@ GC::Ptr SVGGradientElement::linked_gradient(HashTable< auto link = has_attribute(AttributeNames::href) ? get_attribute(AttributeNames::href) : get_attribute("xlink:href"_fly_string); if (auto href = link; href.has_value() && !link->is_empty()) { - auto url = document().parse_url(*href); + auto url = document().encoding_parse_url(*href); auto id = url.fragment(); if (!id.has_value() || id->is_empty()) return {}; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 5b4da377ae92b..35dd36dcbbfda 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -3626,7 +3626,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) } } - if (!has_keyword && !did_set_to_missing_value) + if (!has_keyword && !did_set_to_missing_value) retval = "@invalid_enum_default_value@"_string; )~~~"); @@ -3782,9 +3782,9 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) if (!content_attribute_value.has_value()) return JS::PrimitiveString::create(vm, String {}); - auto url_string = impl->document().parse_url(*content_attribute_value); - if (url_string.is_valid()) - return JS::PrimitiveString::create(vm, url_string.to_string()); + auto url_string = impl->document().encoding_parse_and_serialize_url(*content_attribute_value); + if (url_string.has_value()) + return JS::PrimitiveString::create(vm, url_string.release_value()); )~~~"); } diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index c7fa9d2ab4d07..03944c68bc6fb 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -432,7 +432,7 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString const& request, load_html(page_id, "

Failed to find <link rel="match" /> or <link rel="mismatch" /> in ref test page!

Make sure you added it."); } else { auto link = maybe_link.release_value(); - auto url = document->parse_url(link->get_attribute_value(Web::HTML::AttributeNames::href)); + auto url = document->encoding_parse_url(link->get_attribute_value(Web::HTML::AttributeNames::href)); if (url.query().has_value() && !url.query()->is_empty()) { load_html(page_id, "

Invalid ref test link - query string must be empty

"); return; From d835a00bee393cb73eec233e5728fd883ba0f7d1 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 7 Dec 2024 08:49:29 -0500 Subject: [PATCH 050/237] LibWeb: Use Fetch to retrieve HTMLObjectElement data URLs This eliminates the use of ResourceLoader in HTMLObjectElement. The spec steps around fetching have been slightly updated since we've last looked at this, so those are updated here. Regarding the text test change: we cannot rely on the data: URL being fetched synchronously. It will occur on a deferred task now. This does match the behavior of other browsers, as they also will not have run the fallback representation steps as of DOMContentLoaded. --- Libraries/LibWeb/HTML/HTMLObjectElement.cpp | 309 +++++++++++------- Libraries/LibWeb/HTML/HTMLObjectElement.h | 16 +- ...th-unsupported-type-in-data-attribute.html | 17 +- 3 files changed, 211 insertions(+), 131 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp index 64681cf0a8563..708ac29a620e1 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp @@ -11,8 +11,12 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include #include @@ -39,8 +43,8 @@ HTMLObjectElement::HTMLObjectElement(DOM::Document& document, DOM::QualifiedName // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element // Whenever one of the following conditions occur: // - the element is created, - // ...the user agent must queue an element task on the DOM manipulation task source given - // the object element to run the following steps to (re)determine what the object element represents. + // ...the user agent must queue an element task on the DOM manipulation task source given the object element to run + // the following steps to (re)determine what the object element represents. queue_element_task_to_run_object_representation_steps(); } @@ -69,10 +73,8 @@ void HTMLObjectElement::form_associated_element_attribute_changed(FlyString cons (!has_attribute(HTML::AttributeNames::classid) && name == HTML::AttributeNames::data) || // - neither the element's classid attribute nor its data attribute are present, and its type attribute is set, changed, or removed, (!has_attribute(HTML::AttributeNames::classid) && !has_attribute(HTML::AttributeNames::data) && name == HTML::AttributeNames::type)) { - - // ...the user agent must queue an element task on the DOM manipulation task source given - // the object element to run the following steps to (re)determine what the object element represents. - // This task being queued or actively running must delay the load event of the element's node document. + // ...the user agent must queue an element task on the DOM manipulation task source given the object element to run + // the following steps to (re)determine what the object element represents. queue_element_task_to_run_object_representation_steps(); } } @@ -145,7 +147,7 @@ GC::Ptr HTMLObjectElement::create_layout_node(CSS::StyleProperties switch (m_representation) { case Representation::Children: return NavigableContainer::create_layout_node(move(style)); - case Representation::NestedBrowsingContext: + case Representation::ContentNavigable: return heap().allocate(document(), *this, move(style)); case Representation::Image: if (image_data()) @@ -184,152 +186,213 @@ bool HTMLObjectElement::has_ancestor_media_element_or_object_element_not_showing // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:queue-an-element-task void HTMLObjectElement::queue_element_task_to_run_object_representation_steps() { - queue_an_element_task(HTML::Task::Source::DOMManipulation, [&]() { - // FIXME: 1. If the user has indicated a preference that this object element's fallback content be shown instead of the element's usual behavior, then jump to the step below labeled fallback. - - // 2. If the element has an ancestor media element, or has an ancestor object element that is not showing its fallback content, or if the element is not in a document whose browsing context is non-null, or if the element's node document is not fully active, or if the element is still in the stack of open elements of an HTML parser or XML parser, or if the element is not being rendered, then jump to the step below labeled fallback. - if (!document().browsing_context() || !document().is_fully_active()) - return run_object_representation_fallback_steps(); - if (has_ancestor_media_element_or_object_element_not_showing_fallback_content()) - return run_object_representation_fallback_steps(); - - // FIXME: 3. If the classid attribute is present, and has a value that isn't the empty string, then: if the user agent can find a plugin suitable according to the value of the classid attribute, and plugins aren't being sandboxed, then that plugin should be used, and the value of the data attribute, if any, should be passed to the plugin. If no suitable plugin can be found, or if the plugin reports an error, jump to the step below labeled fallback. + // This task being queued or actively running must delay the load event of the element's node document. + m_document_load_event_delayer_for_object_representation_task.emplace(document()); + + queue_an_element_task(HTML::Task::Source::DOMManipulation, [this]() { + ScopeGuard guard { [&]() { m_document_load_event_delayer_for_object_representation_task.clear(); } }; + + auto& realm = this->realm(); + auto& vm = realm.vm(); + + // FIXME: 1. If the user has indicated a preference that this object element's fallback content be shown instead of the + // element's usual behavior, then jump to the step below labeled fallback. + + // 2. If the element has an ancestor media element, or has an ancestor object element that is not showing its + // fallback content, or if the element is not in a document whose browsing context is non-null, or if the + // element's node document is not fully active, or if the element is still in the stack of open elements of + // an HTML parser or XML parser, or if the element is not being rendered, then jump to the step below labeled + // fallback. + // FIXME: Handle the element being in the stack of open elements. + // FIXME: Handle the element not being rendered. + if (!document().browsing_context() || !document().is_fully_active()) { + run_object_representation_fallback_steps(); + return; + } + if (has_ancestor_media_element_or_object_element_not_showing_fallback_content()) { + run_object_representation_fallback_steps(); + return; + } - // 4. If the data attribute is present and its value is not the empty string, then: - if (auto maybe_data = get_attribute(HTML::AttributeNames::data); maybe_data.has_value() && !maybe_data->is_empty()) { - // 1. If the type attribute is present and its value is not a type that the user agent supports, and is not a type that the user agent can find a plugin for, then the user agent may jump to the step below labeled fallback without fetching the content to examine its real type. + // 3. If the data attribute is present and its value is not the empty string, then: + if (auto data = get_attribute(HTML::AttributeNames::data); data.has_value() && !data->is_empty()) { + // 1. If the type attribute is present and its value is not a type that the user agent supports, then the user + // agent may jump to the step below labeled fallback without fetching the content to examine its real type. - // 2. Parse a URL given the data attribute, relative to the element's node document. - auto url = document().parse_url(*maybe_data); + // 2. Let url be the result of encoding-parsing a URL given the data attribute's value, relative to the element's node document. + auto url = document().encoding_parse_url(*data); - // 3. If that failed, fire an event named error at the element, then jump to the step below labeled fallback. + // 3. If url is failure, then fire an event named error at the element and jump to the step below labeled fallback. if (!url.is_valid()) { - dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); - return run_object_representation_fallback_steps(); + dispatch_event(DOM::Event::create(realm, HTML::EventNames::error)); + run_object_representation_fallback_steps(); + return; } - // 4. Let request be a new request whose URL is the resulting URL record, client is the element's node document's relevant settings object, destination is "object", credentials mode is "include", mode is "navigate", and whose use-URL-credentials flag is set. - auto request = LoadRequest::create_for_url_on_page(url, &document().page()); + // 4. Let request be a new request whose URL is url, client is the element's node document's relevant settings + // object, destination is "object", credentials mode is "include", mode is "navigate", initiator type is + // "object", and whose use-URL-credentials flag is set. + auto request = Fetch::Infrastructure::Request::create(vm); + request->set_url(move(url)); + request->set_client(&document().relevant_settings_object()); + request->set_destination(Fetch::Infrastructure::Request::Destination::Object); + request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include); + request->set_mode(Fetch::Infrastructure::Request::Mode::Navigate); + request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Object); + request->set_use_url_credentials(true); + + Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; + fetch_algorithms_input.process_response = [this](GC::Ref response) { + auto& realm = this->realm(); + auto& global = document().realm().global_object(); + + if (response->is_network_error()) { + resource_did_fail(); + return; + } + + if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) { + auto& filtered_response = static_cast(*response); + response = filtered_response.internal_response(); + } + + auto on_data_read = GC::create_function(realm.heap(), [this, response](ByteBuffer data) { + resource_did_load(response, data); + }); + auto on_error = GC::create_function(realm.heap(), [this](JS::Value) { + resource_did_fail(); + }); + + VERIFY(response->body()); + response->body()->fully_read(realm, on_data_read, on_error, GC::Ref { global }); + }; + + // 5. Fetch request. + auto result = Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))); + if (result.is_error()) { + resource_did_fail(); + return; + } - // 5. Fetch request, with processResponseEndOfBody given response res set to finalize and report timing with res, the element's node document's relevant global object, and "object". - // Fetching the resource must delay the load event of the element's node document until the task that is queued by the networking task source once the resource has been fetched (defined next) has been run. - set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); + // Fetching the resource must delay the load event of the element's node document until the task that is + // queued by the networking task source once the resource has been fetched (defined next) has been run. m_document_load_event_delayer_for_resource_load.emplace(document()); - // 6. If the resource is not yet available (e.g. because the resource was not available in the cache, so that loading the resource required making a request over the network), then jump to the step below labeled fallback. The task that is queued by the networking task source once the resource is available must restart this algorithm from this step. Resources can load incrementally; user agents may opt to consider a resource "available" whenever enough data has been obtained to begin processing the resource. + // 6. If the resource is not yet available (e.g. because the resource was not available in the cache, so that + // loading the resource required making a request over the network), then jump to the step below labeled + // fallback. The task that is queued by the networking task source once the resource is available must + // restart this algorithm from this step. Resources can load incrementally; user agents may opt to consider + // a resource "available" whenever enough data has been obtained to begin processing the resource. - // NOTE: The request is always asynchronous, even if it is cached or succeeded/failed immediately. Allow the callbacks below to invoke - // the fallback steps. This prevents the fallback layout from flashing very briefly between here and the resource loading. - return; + // NOTE: The request is always asynchronous, even if it is cached or succeeded/failed immediately. Allow the + // response callback to invoke the fallback steps. This prevents the fallback layout from flashing very + // briefly between here and the resource loading. } - - // 5. If the data attribute is absent but the type attribute is present, and the user agent can find a plugin suitable according to the value of the type attribute, and plugins aren't being sandboxed, then that plugin should be used. If these conditions cannot be met, or if the plugin reports an error, jump to the step below labeled fallback. Otherwise return; once the plugin is completely loaded, queue an element task on the DOM manipulation task source given the object element to fire an event named load at the element. - run_object_representation_fallback_steps(); }); } // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:concept-event-fire-2 void HTMLObjectElement::resource_did_fail() { - // 4.7. If the load failed (e.g. there was an HTTP 404 error, there was a DNS error), fire an event named error at the element, then jump to the step below labeled fallback. + ScopeGuard guard { [&]() { m_document_load_event_delayer_for_resource_load.clear(); } }; + + // 3.7. If the load failed (e.g. there was an HTTP 404 error, there was a DNS error), fire an event named error at + // the element, then jump to the step below labeled fallback. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error)); run_object_representation_fallback_steps(); - m_document_load_event_delayer_for_resource_load.clear(); } // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#object-type-detection -void HTMLObjectElement::resource_did_load() +void HTMLObjectElement::resource_did_load(Fetch::Infrastructure::Response const& response, ReadonlyBytes data) { - ScopeGuard load_event_delayer_guard = [&] { - m_document_load_event_delayer_for_resource_load.clear(); - }; + ScopeGuard guard { [&]() { m_document_load_event_delayer_for_resource_load.clear(); } }; - // 4.8. Determine the resource type, as follows: + // 3.8. Determine the resource type, as follows: // 1. Let the resource type be unknown. - Optional resource_type; + Optional resource_type; - // FIXME: 2. If the user agent is configured to strictly obey Content-Type headers for this resource, and the resource has associated Content-Type metadata, then let the resource type be the type specified in the resource's Content-Type metadata, and jump to the step below labeled handler. - // FIXME: 3. If there is a type attribute present on the object element, and that attribute's value is not a type that the user agent supports, but it is a type that a plugin supports, then let the resource type be the type specified in that type attribute, and jump to the step below labeled handler. - - // 4. Run the appropriate set of steps from the following list: - // * If the resource has associated Content-Type metadata - if (auto maybe_content_type = resource()->response_headers().get("Content-Type"sv); maybe_content_type.has_value()) { - auto& content_type = maybe_content_type.value(); + // FIXME: 3. If the user agent is configured to strictly obey Content-Type headers for this resource, and the resource has + // associated Content-Type metadata, then let the resource type be the type specified in the resource's Content-Type + // metadata, and jump to the step below labeled handler. + // 3. Run the appropriate set of steps from the following list: + // -> If the resource has associated Content-Type metadata + if (auto content_type = response.header_list()->extract_mime_type(); content_type.has_value()) { // 1. Let binary be false. bool binary = false; - // 2. If the type specified in the resource's Content-Type metadata is "text/plain", and the result of applying the rules for distinguishing if a resource is text or binary to the resource is that the resource is not text/plain, then set binary to true. - if (content_type == "text/plain"sv) { - auto supplied_type = MimeSniff::MimeType::parse(content_type); - auto computed_type = MimeSniff::Resource::sniff(resource()->encoded_data(), MimeSniff::SniffingConfiguration { - .sniffing_context = MimeSniff::SniffingContext::TextOrBinary, - .supplied_type = move(supplied_type), - }); + // 2. If the type specified in the resource's Content-Type metadata is "text/plain", and the result of applying + // the rules for distinguishing if a resource is text or binary to the resource is that the resource is not + // text/plain, then set binary to true. + if (content_type->essence() == "text/plain"sv) { + auto computed_type = MimeSniff::Resource::sniff( + data, + MimeSniff::SniffingConfiguration { + .sniffing_context = MimeSniff::SniffingContext::TextOrBinary, + .supplied_type = content_type, + }); + if (computed_type.essence() != "text/plain"sv) binary = true; } // 3. If the type specified in the resource's Content-Type metadata is "application/octet-stream", then set binary to true. - if (content_type == "application/octet-stream"sv) + else if (content_type->essence() == "application/octet-stream"sv) { binary = true; + } + + // 4. If binary is false, then let the resource type be the type specified in the resource's Content-Type metadata, + // and jump to the step below labeled handler. + if (!binary) { + resource_type = move(content_type); + } - // 4. If binary is false, then let the resource type be the type specified in the resource's Content-Type metadata, and jump to the step below labeled handler. - if (!binary) - return run_object_representation_handler_steps(content_type); + // 5. If there is a type attribute present on the object element, and its value is not application/octet-stream, + // then run the following steps: + else if (auto type = this->type(); !type.is_empty() && (type != "application/octet-stream"sv)) { + // 1. If the attribute's value is a type that starts with "image/" that is not also an XML MIME type, then + // let the resource type be the type specified in that type attribute. + if (type.starts_with_bytes("image/"sv)) { + auto parsed_type = MimeSniff::MimeType::parse(type); - // 5. If there is a type attribute present on the object element, and its value is not application/octet-stream, then run the following steps: - if (auto type = this->type(); !type.is_empty() && (type != "application/octet-stream"sv)) { - // 1. If the attribute's value is a type that a plugin supports, or the attribute's value is a type that starts with "image/" that is not also an XML MIME type, then let the resource type be the type specified in that type attribute. - // FIXME: This only partially implements this step. - if (type.starts_with_bytes("image/"sv)) - resource_type = move(type); + if (parsed_type.has_value() && !parsed_type->is_xml()) + resource_type = move(parsed_type); + } // 2. Jump to the step below labeled handler. } } - // * Otherwise, if the resource does not have associated Content-Type metadata + // -> Otherwise, if the resource does not have associated Content-Type metadata else { - Optional tentative_type; + Optional tentative_type; // 1. If there is a type attribute present on the object element, then let the tentative type be the type specified in that type attribute. // Otherwise, let tentative type be the computed type of the resource. if (auto type = this->type(); !type.is_empty()) - tentative_type = move(type); + tentative_type = MimeSniff::MimeType::parse(type); + else + tentative_type = MimeSniff::Resource::sniff(data); - // FIXME: For now, ignore application/ MIME types as we cannot render yet them anyways. We will need to implement the MIME type sniffing - // algorithm in order to map all unknown MIME types to "application/octet-stream". - else if (auto type = resource()->mime_type(); !type.starts_with("application/"sv)) - tentative_type = MUST(String::from_byte_string(type)); - - // 2. If tentative type is not application/octet-stream, then let resource type be tentative type and jump to the step below labeled handler. - if (tentative_type.has_value() && tentative_type != "application/octet-stream"sv) + // 2. If tentative type is not application/octet-stream, then let resource type be tentative type and jump to the + // step below labeled handler. + if (tentative_type.has_value() && tentative_type->essence() != "application/octet-stream"sv) resource_type = move(tentative_type); } - // FIXME: 5. If applying the URL parser algorithm to the URL of the specified resource (after any redirects) results in a URL record whose path component matches a pattern that a plugin supports, then let resource type be the type that that plugin can handle. - run_object_representation_handler_steps(resource_type.has_value() ? resource_type->to_byte_string() : ByteString::empty()); + if (resource_type.has_value()) + run_object_representation_handler_steps(response, *resource_type, data); + else + run_object_representation_fallback_steps(); } // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:plugin-11 -void HTMLObjectElement::run_object_representation_handler_steps(Optional resource_type) +void HTMLObjectElement::run_object_representation_handler_steps(Fetch::Infrastructure::Response const& response, MimeSniff::MimeType const& resource_type, ReadonlyBytes data) { - // 4.9. Handler: Handle the content as given by the first of the following cases that matches: - - // * FIXME: If the resource type is not a type that the user agent supports, but it is a type that a plugin supports - // If the object element's nested browsing context is non-null, then it must be discarded and then set to null. - // If plugins are being sandboxed, then jump to the step below labeled fallback. - // Otherwise, the user agent should use the plugin that supports resource type and pass the content of the resource to that plugin. If the plugin reports an error, then jump to the step below labeled fallback. - - if (!resource_type.has_value()) { - run_object_representation_fallback_steps(); - return; - } - auto mime_type = MimeSniff::MimeType::parse(*resource_type); + // 3.9. Handler: Handle the content as given by the first of the following cases that matches: - // * If the resource type is an XML MIME type, or if the resource type does not start with "image/" - if (mime_type.has_value() && can_load_document_with_type(*mime_type) && (mime_type->is_xml() || !mime_type->is_image())) { + // -> If the resource type is an XML MIME type, or if the resource type does not start with "image/" + if (can_load_document_with_type(resource_type) && (resource_type.is_xml() || !resource_type.is_image())) { // If the object element's content navigable is null, then create a new child navigable for the element. if (!m_content_navigable && in_a_document_tree()) { MUST(create_new_child_navigable()); @@ -340,32 +403,41 @@ void HTMLObjectElement::run_object_representation_handler_steps(Optionalurl(); url != "about:blank"sv) - MUST(m_content_navigable->navigate({ .url = url, + // Let response be the response from fetch. + + // If response's URL does not match about:blank, then navigate the element's content navigable to response's URL + // using the element's node document, with historyHandling set to "replace". + if (response.url().has_value() && !url_matches_about_blank(*response.url())) { + MUST(m_content_navigable->navigate({ + .url = *response.url(), .source_document = document(), - .history_handling = Bindings::NavigationHistoryBehavior::Replace })); + .history_handling = Bindings::NavigationHistoryBehavior::Replace, + })); + } - // The object element represents its nested browsing context. - run_object_representation_completed_steps(Representation::NestedBrowsingContext); + // The object element represents its content navigable. + run_object_representation_completed_steps(Representation::ContentNavigable); } - // * If the resource type starts with "image/", and support for images has not been disabled + // -> If the resource type starts with "image/", and support for images has not been disabled // FIXME: Handle disabling image support. - else if (resource_type.has_value() && resource_type->starts_with("image/"sv)) { - // Destroy the child navigable of the object element. + else if (resource_type.is_image()) { + // Destroy a child navigable given the object element. destroy_the_child_navigable(); // Apply the image sniffing rules to determine the type of the image. // The object element represents the specified image. - // If the image cannot be rendered, e.g. because it is malformed or in an unsupported format, jump to the step below labeled fallback. - if (!resource()->has_encoded_data()) - return run_object_representation_fallback_steps(); + // If the image cannot be rendered, e.g. because it is malformed or in an unsupported format, jump to the step + // below labeled fallback. + if (data.is_empty()) { + run_object_representation_fallback_steps(); + return; + } load_image(); } - // * Otherwise + // -> Otherwise else { // The given resource type is not supported. Jump to the step below labeled fallback. run_object_representation_fallback_steps(); @@ -375,9 +447,12 @@ void HTMLObjectElement::run_object_representation_handler_steps(Optional([](auto& object) { object.queue_element_task_to_run_object_representation_steps(); return IterationDecision::Continue; diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.h b/Libraries/LibWeb/HTML/HTMLObjectElement.h index 960ac1f6bab04..4617ef7a4d5ca 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.h +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.h @@ -12,14 +12,12 @@ #include #include #include -#include namespace Web::HTML { class HTMLObjectElement final : public NavigableContainer , public FormAssociatedElement - , public ResourceClient , public Layout::ImageProvider { WEB_PLATFORM_OBJECT(HTMLObjectElement, NavigableContainer) GC_DECLARE_ALLOCATOR(HTMLObjectElement); @@ -28,7 +26,7 @@ class HTMLObjectElement final enum class Representation { Unknown, Image, - NestedBrowsingContext, + ContentNavigable, Children, }; @@ -64,16 +62,15 @@ class HTMLObjectElement final bool has_ancestor_media_element_or_object_element_not_showing_fallback_content() const; void queue_element_task_to_run_object_representation_steps(); - void run_object_representation_handler_steps(Optional resource_type); + void run_object_representation_handler_steps(Fetch::Infrastructure::Response const&, MimeSniff::MimeType const&, ReadonlyBytes); void run_object_representation_completed_steps(Representation); void run_object_representation_fallback_steps(); void load_image(); void update_layout_and_child_objects(Representation); - // ^ResourceClient - virtual void resource_did_load() override; - virtual void resource_did_fail() override; + void resource_did_load(Fetch::Infrastructure::Response const&, ReadonlyBytes); + void resource_did_fail(); // ^DOM::Element virtual i32 default_tab_index_value() const override; @@ -87,13 +84,12 @@ class HTMLObjectElement final virtual void set_visible_in_viewport(bool) override; virtual GC::Ref to_html_element() const override { return *this; } - Representation m_representation { Representation::Unknown }; - GC::Ptr image_data() const; + Representation m_representation { Representation::Unknown }; + GC::Ptr m_resource_request; -public: Optional m_document_load_event_delayer_for_object_representation_task; Optional m_document_load_event_delayer_for_resource_load; }; diff --git a/Tests/LibWeb/Text/input/object-with-unsupported-type-in-data-attribute.html b/Tests/LibWeb/Text/input/object-with-unsupported-type-in-data-attribute.html index aeffa1453007d..c1e29db00a924 100644 --- a/Tests/LibWeb/Text/input/object-with-unsupported-type-in-data-attribute.html +++ b/Tests/LibWeb/Text/input/object-with-unsupported-type-in-data-attribute.html @@ -1,10 +1,19 @@ -
Fallback
+
Fallback
From e4fb25bf636b0d4e2b3ba18b33d10008aa8af99d Mon Sep 17 00:00:00 2001 From: rmg-x Date: Sun, 8 Dec 2024 11:51:18 -0600 Subject: [PATCH 051/237] LibGfx: Redirect PNG errors and warnings to our own logging functions Before, libpng would use its own internal logging mechanism to print non-fatal errors and warnings to stdout/stderr. This made it confusing when trying to search the Ladybird codebase for those messages as they didn't exist. This commit uses `png_set_error_fn` from libpng to redirect those messages to our own custom logging functions instead. --- Libraries/LibGfx/ImageFormats/PNGLoader.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp index c1901bab459c5..ae581d4dd0066 100644 --- a/Libraries/LibGfx/ImageFormats/PNGLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/PNGLoader.cpp @@ -82,6 +82,17 @@ ErrorOr> PNGImageDecoderPlugin::icc_data() return OptionalNone {}; } +static void log_png_error(png_structp png_ptr, char const* error_message) +{ + dbgln("libpng error: {}", error_message); + png_longjmp(png_ptr, 1); +} + +static void log_png_warning(png_structp, char const* warning_message) +{ + dbgln("libpng warning: {}", warning_message); +} + ErrorOr PNGImageDecoderPlugin::initialize() { png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); @@ -109,6 +120,8 @@ ErrorOr PNGImageDecoderPlugin::initialize() *read_data = read_data->slice(length); }); + png_set_error_fn(png_ptr, nullptr, log_png_error, log_png_warning); + png_read_info(png_ptr, info_ptr); u32 width = 0; From 4b069344e0ad35124eccc9ec0f9e83ba295af97a Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 11 Dec 2024 10:38:16 +1300 Subject: [PATCH 052/237] LibWeb/HTML: Fix crash in window open steps on empty URL string --- Libraries/LibWeb/HTML/Window.cpp | 4 ++-- .../LibWeb/Text/expected/HTML/Window-open-empty-string.txt | 1 + Tests/LibWeb/Text/input/HTML/Window-open-empty-string.html | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/Window-open-empty-string.txt create mode 100644 Tests/LibWeb/Text/input/HTML/Window-open-empty-string.html diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index dd0774ee5e024..c57ff134a81e5 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -221,8 +221,8 @@ WebIDL::ExceptionOr Window::window_open_steps_internal(Str } // 9. Let noopener be the result of getting noopener for window open with sourceDocument, tokenizedFeatures, and urlRecord. - // FIXME: Is it safe to assume url_record has a value here? - auto no_opener = get_noopener_for_window_open(source_document, tokenized_features, *url_record); + // FIXME: Spec bug: https://github.com/whatwg/html/issues/10844 + auto no_opener = get_noopener_for_window_open(source_document, tokenized_features, url_record.has_value() ? *url_record : URL::URL("about:blank")); // 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"]. tokenized_features.remove("noopener"sv); diff --git a/Tests/LibWeb/Text/expected/HTML/Window-open-empty-string.txt b/Tests/LibWeb/Text/expected/HTML/Window-open-empty-string.txt new file mode 100644 index 0000000000000..50586a4cbfb7c --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/Window-open-empty-string.txt @@ -0,0 +1 @@ +PASS! (Didn't crash) diff --git a/Tests/LibWeb/Text/input/HTML/Window-open-empty-string.html b/Tests/LibWeb/Text/input/HTML/Window-open-empty-string.html new file mode 100644 index 0000000000000..c10b55a51bb71 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/Window-open-empty-string.html @@ -0,0 +1,7 @@ + + From 1ff556a2bd10bd891b7aead777d78fb2c5e606a2 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 10 Dec 2024 16:37:43 +0000 Subject: [PATCH 053/237] Tests: Move Ladybird specific testdriver code to `testdriver-vendor.js` --- .../input/wpt-import/resources/testdriver-vendor.js | 8 +++++++- .../Text/input/wpt-import/resources/testdriver.js | 12 ++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js index 3e88403636396..2496b5f1830bc 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js @@ -1 +1,7 @@ -// This file intentionally left blank +window.test_driver_internal.get_computed_label = async function(element) { + return await window.internals.getComputedLabel(element); +}; + +window.test_driver_internal.get_computed_role = async function(element) { + return await window.internals.getComputedRole(element); +}; diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js index 47a5ec412f6f7..2d1a89690cc25 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js @@ -233,11 +233,7 @@ * rejected in the cases the WebDriver command errors */ get_computed_label: async function(element) { - // XXX: Ladybird-specific change: Upstream WPT calls - // window.test_driver_internal.get_computed_label(element) here, - // but we’ve changed that to our Ladybird-specific - // window.internals.getComputedLabel(el). - let label = await window.internals.getComputedLabel(element); + let label = await window.test_driver_internal.get_computed_label(element); return label; }, @@ -254,11 +250,7 @@ * rejected in the cases the WebDriver command errors */ get_computed_role: async function(element) { - // XXX: Ladybird-specific change: Upstream WPT calls - // window.test_driver_internal.get_computed_role(element) here, - // but we’ve changed that to our Ladybird-specific - // window.internals.getComputedRole(el). - let role = await window.internals.getComputedRole(element); + let role = await window.test_driver_internal.get_computed_role(element); return role; }, From 1bd10a5443982d5cad1da1183b988f4a18c2da81 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 10 Dec 2024 16:41:37 +0000 Subject: [PATCH 054/237] Tests: Add vendor-specific testdriver `click()` function This uses `Internals.click()` which doesn't do as much as the equivalent WebDriver method, but should be enough to satify most tests that use it. --- .../infrastructure/testdriver/click.txt | 6 ++++++ .../infrastructure/testdriver/click.html | 19 +++++++++++++++++++ .../wpt-import/resources/testdriver-vendor.js | 10 ++++++++++ 3 files changed, 35 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/click.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/click.html diff --git a/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/click.txt b/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/click.txt new file mode 100644 index 0000000000000..81464bc95c393 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/click.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass TestDriver click method \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/click.html b/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/click.html new file mode 100644 index 0000000000000..0d144b3d33e74 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/click.html @@ -0,0 +1,19 @@ + + +TestDriver click method + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js index 2496b5f1830bc..a9beda369b152 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js @@ -1,3 +1,13 @@ +window.test_driver_internal.click = function(element) { + const boundingRect = element.getBoundingClientRect(); + const centerPoint = { + x: boundingRect.left + boundingRect.width / 2, + y: boundingRect.top + boundingRect.height / 2 + }; + window.internals.click(centerPoint.x, centerPoint.y); + return Promise.resolve(); +}; + window.test_driver_internal.get_computed_label = async function(element) { return await window.internals.getComputedLabel(element); }; From a536ee6f31874d833853f7704ae58c5e8ceb27d1 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 10 Dec 2024 16:44:42 +0000 Subject: [PATCH 055/237] Tests: Add vendor-specific testdriver `send_keys()` function This uses the `Internals.sendText()` function. --- .../infrastructure/testdriver/send_keys.txt | 6 +++++ .../infrastructure/testdriver/send_keys.html | 23 +++++++++++++++++++ .../wpt-import/resources/testdriver-vendor.js | 5 ++++ 3 files changed, 34 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/send_keys.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/send_keys.html diff --git a/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/send_keys.txt b/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/send_keys.txt new file mode 100644 index 0000000000000..dc11002070047 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/infrastructure/testdriver/send_keys.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass TestDriver send keys method \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/send_keys.html b/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/send_keys.html new file mode 100644 index 0000000000000..933d8d7f936a6 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/infrastructure/testdriver/send_keys.html @@ -0,0 +1,23 @@ + + +TestDriver send keys method + + + + + +Text Input + + diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js index a9beda369b152..54b62143917ae 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js @@ -8,6 +8,11 @@ window.test_driver_internal.click = function(element) { return Promise.resolve(); }; +window.test_driver_internal.send_keys = function(element, keys) { + window.internals.sendText(element, keys); + return Promise.resolve(); +} + window.test_driver_internal.get_computed_label = async function(element) { return await window.internals.getComputedLabel(element); }; From f110edebd16fc14fb16d31bd1a3627e6ca80776f Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 11 Dec 2024 21:01:22 +1300 Subject: [PATCH 056/237] LibWeb/HTML: Encoding parse a URL when setting a href URL Fixes many WPT encoding regression tests which regressed in fe891727dc264e23d34b877c8b6da4df80b07bc0. --- .../LibWeb/HTML/HTMLHyperlinkElementUtils.cpp | 17 ++++++++++++----- .../HTML/href-iso-2022-jp-url-encoding.txt | 1 + .../HTML/href-iso-2022-jp-url-encoding.html | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/href-iso-2022-jp-url-encoding.txt create mode 100644 Tests/LibWeb/Text/input/HTML/href-iso-2022-jp-url-encoding.html diff --git a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp index 2e823aa2b36d0..eb0698d2bfeb2 100644 --- a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp +++ b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -27,17 +28,23 @@ void HTMLHyperlinkElementUtils::reinitialize_url() const // https://html.spec.whatwg.org/multipage/links.html#concept-hyperlink-url-set void HTMLHyperlinkElementUtils::set_the_url() { - // 1. If this element's href content attribute is absent, set this element's url to null. + // 1. Set this element's url to null. + m_url = {}; + + // 2. If this element's href content attribute is absent, then return. auto href_content_attribute = hyperlink_element_utils_href(); if (!href_content_attribute.has_value()) { - m_url = {}; hyperlink_element_utils_element().invalidate_style(DOM::StyleInvalidationReason::HTMLHyperlinkElementHrefChange); return; } - // 2. Otherwise, parse this element's href content attribute value relative to this element's node document. - // If parsing is successful, set this element's url to the result; otherwise, set this element's url to null. - m_url = hyperlink_element_utils_document().parse_url(*href_content_attribute); + // 3. Let url be the result of encoding-parsing a URL given this element's href content attribute's value, relative to this element's node document. + auto url = hyperlink_element_utils_document().encoding_parse_url(*href_content_attribute); + + // 4. If url is not failure, then set this element's url to url. + if (url.is_valid()) + m_url = move(url); + hyperlink_element_utils_element().invalidate_style(DOM::StyleInvalidationReason::HTMLHyperlinkElementHrefChange); } diff --git a/Tests/LibWeb/Text/expected/HTML/href-iso-2022-jp-url-encoding.txt b/Tests/LibWeb/Text/expected/HTML/href-iso-2022-jp-url-encoding.txt new file mode 100644 index 0000000000000..b0fdeec0f85e3 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/href-iso-2022-jp-url-encoding.txt @@ -0,0 +1 @@ +%26%2319973%3B diff --git a/Tests/LibWeb/Text/input/HTML/href-iso-2022-jp-url-encoding.html b/Tests/LibWeb/Text/input/HTML/href-iso-2022-jp-url-encoding.html new file mode 100644 index 0000000000000..29b77f2ef81a4 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/href-iso-2022-jp-url-encoding.html @@ -0,0 +1,16 @@ + + + + + + + + + From c6d0f87bb75d5adb73f004c89907018e4cd9bbfd Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 8 Dec 2024 17:12:11 +1300 Subject: [PATCH 057/237] LibWeb/Streams: Put algorithm definitions in a separate header file To solve a future cyclic dependency problem. --- Libraries/LibWeb/Streams/AbstractOperations.h | 11 +------- Libraries/LibWeb/Streams/Algorithms.h | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Libraries/LibWeb/Streams/Algorithms.h diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index 7dff547a67c93..11d143558e748 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -19,16 +20,6 @@ namespace Web::Streams { -using SizeAlgorithm = GC::Function; -using PullAlgorithm = GC::Function()>; -using CancelAlgorithm = GC::Function(JS::Value)>; -using StartAlgorithm = GC::Function()>; -using AbortAlgorithm = GC::Function(JS::Value)>; -using CloseAlgorithm = GC::Function()>; -using WriteAlgorithm = GC::Function(JS::Value)>; -using FlushAlgorithm = GC::Function()>; -using TransformAlgorithm = GC::Function(JS::Value)>; - WebIDL::ExceptionOr> acquire_readable_stream_default_reader(ReadableStream&); WebIDL::ExceptionOr> acquire_readable_stream_byob_reader(ReadableStream&); bool is_readable_stream_locked(ReadableStream const&); diff --git a/Libraries/LibWeb/Streams/Algorithms.h b/Libraries/LibWeb/Streams/Algorithms.h new file mode 100644 index 0000000000000..9a486a6a7f540 --- /dev/null +++ b/Libraries/LibWeb/Streams/Algorithms.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::Streams { + +using SizeAlgorithm = GC::Function; +using PullAlgorithm = GC::Function()>; +using CancelAlgorithm = GC::Function(JS::Value)>; +using StartAlgorithm = GC::Function()>; +using AbortAlgorithm = GC::Function(JS::Value)>; +using CloseAlgorithm = GC::Function()>; +using WriteAlgorithm = GC::Function(JS::Value)>; +using FlushAlgorithm = GC::Function()>; +using TransformAlgorithm = GC::Function(JS::Value)>; + +} From 3f572d9ab7a1c7ef49e8f050a6e2e379dbec81a9 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 8 Dec 2024 17:28:44 +1300 Subject: [PATCH 058/237] LibWeb/Streams: Move ReadableStream functions out of AbstractOperations These are not defined in the abstract operations section of the spec and are the publically exported Stream APIs exposed on ReadableStream. --- Libraries/LibWeb/Fetch/BodyInit.cpp | 4 +- .../Fetch/Fetching/FetchedDataReceiver.cpp | 2 +- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 2 +- Libraries/LibWeb/FileAPI/Blob.cpp | 4 +- .../LibWeb/Streams/AbstractOperations.cpp | 122 ----------------- Libraries/LibWeb/Streams/AbstractOperations.h | 3 - Libraries/LibWeb/Streams/ReadableStream.cpp | 125 ++++++++++++++++++ Libraries/LibWeb/Streams/ReadableStream.h | 5 + 8 files changed, 136 insertions(+), 131 deletions(-) diff --git a/Libraries/LibWeb/Fetch/BodyInit.cpp b/Libraries/LibWeb/Fetch/BodyInit.cpp index b5e6e43af02b8..66e3a72125313 100644 --- a/Libraries/LibWeb/Fetch/BodyInit.cpp +++ b/Libraries/LibWeb/Fetch/BodyInit.cpp @@ -56,7 +56,7 @@ WebIDL::ExceptionOr extract_body(JS::Realm& realm, // 4. Otherwise, set stream to a new ReadableStream object, and set up stream with byte reading support. else { stream = realm.create(realm); - Streams::set_up_readable_stream_controller_with_byte_reading_support(*stream); + stream->set_up_with_byte_reading_support(); } // 5. Assert: stream is a ReadableStream object. @@ -156,7 +156,7 @@ WebIDL::ExceptionOr extract_body(JS::Realm& realm, auto array_buffer = JS::ArrayBuffer::create(stream->realm(), move(bytes)); auto chunk = JS::Uint8Array::create(stream->realm(), array_buffer->byte_length(), *array_buffer); - Streams::readable_stream_enqueue(*stream->controller(), chunk).release_value_but_fixme_should_propagate_errors(); + stream->enqueue(chunk).release_value_but_fixme_should_propagate_errors(); } // When running action is done, close stream. diff --git a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp index b03d5ff37b27c..95c5f0ce34c0a 100644 --- a/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/FetchedDataReceiver.cpp @@ -68,7 +68,7 @@ void FetchedDataReceiver::on_data_received(ReadonlyBytes bytes) HTML::TemporaryExecutionContext execution_context { m_stream->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 1. Pull from bytes buffer into stream. - if (auto result = Streams::readable_stream_pull_from_bytes(m_stream, move(bytes)); result.is_error()) { + if (auto result = m_stream->pull_from_bytes(move(bytes)); result.is_error()) { auto throw_completion = Bindings::exception_to_throw_completion(m_stream->vm(), result.release_error()); dbgln("FetchedDataReceiver: Stream error pulling bytes"); diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 5fc26010b981c..207f0ad619ed4 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -2305,7 +2305,7 @@ WebIDL::ExceptionOr> nonstandard_resource_loader_file_o }); // 13. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm. - Streams::set_up_readable_stream_controller_with_byte_reading_support(stream, pull_algorithm, cancel_algorithm); + stream->set_up_with_byte_reading_support(pull_algorithm, cancel_algorithm); auto on_headers_received = GC::create_function(vm.heap(), [&vm, request, pending_response, stream](HTTP::HeaderMap const& response_headers, Optional status_code, Optional const& reason_phrase) { (void)request; diff --git a/Libraries/LibWeb/FileAPI/Blob.cpp b/Libraries/LibWeb/FileAPI/Blob.cpp index c750eb245287e..dfa25d083661d 100644 --- a/Libraries/LibWeb/FileAPI/Blob.cpp +++ b/Libraries/LibWeb/FileAPI/Blob.cpp @@ -324,7 +324,7 @@ GC::Ref Blob::get_stream() auto stream = realm.create(realm); // 2. Set up stream with byte reading support. - set_up_readable_stream_controller_with_byte_reading_support(stream); + stream->set_up_with_byte_reading_support(); // FIXME: 3. Run the following steps in parallel: { @@ -346,7 +346,7 @@ GC::Ref Blob::get_stream() // 3. Enqueue chunk in stream. auto maybe_error = Bindings::throw_dom_exception_if_needed(realm.vm(), [&]() { - return readable_stream_enqueue(*stream->controller(), chunk); + return stream->enqueue(chunk); }); if (maybe_error.is_error()) { diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 1ca54b536d996..b34f2c16208a4 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -3259,39 +3259,6 @@ WebIDL::ExceptionOr set_up_readable_byte_stream_controller(ReadableStream& return {}; } -// https://streams.spec.whatwg.org/#readablestream-enqueue -WebIDL::ExceptionOr readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk) -{ - // 1. If stream.[[controller]] implements ReadableStreamDefaultController, - if (controller.has>()) { - // 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk). - return readable_stream_default_controller_enqueue(controller.get>(), chunk); - } - // 2. Otherwise, - else { - // 1. Assert: stream.[[controller]] implements ReadableByteStreamController. - VERIFY(controller.has>()); - auto readable_byte_controller = controller.get>(); - - // FIXME: 2. Assert: chunk is an ArrayBufferView. - - // 3. Let byobView be the current BYOB request view for stream. - // FIXME: This is not what the spec means by 'current BYOB request view' - auto byob_view = readable_byte_controller->raw_byob_request(); - - // 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then: - if (byob_view) { - // FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]]. - // FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]]. - // FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]). - TODO(); - } - - // 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk). - return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk); - } -} - // https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue WebIDL::ExceptionOr readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk) { @@ -3412,49 +3379,6 @@ WebIDL::ExceptionOr readable_byte_stream_controller_enqueue(ReadableByteSt return {}; } -// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes -WebIDL::ExceptionOr readable_stream_pull_from_bytes(ReadableStream& stream, ByteBuffer bytes) -{ - // 1. Assert: stream.[[controller]] implements ReadableByteStreamController. - auto controller = stream.controller()->get>(); - - // 2. Let available be bytes’s length. - auto available = bytes.size(); - - // 3. Let desiredSize be available. - auto desired_size = available; - - // FIXME: 4. If stream’s current BYOB request view is non-null, then set desiredSize to stream’s current BYOB request - // view's byte length. - - // 5. Let pullSize be the smaller value of available and desiredSize. - auto pull_size = min(available, desired_size); - - // 6. Let pulled be the first pullSize bytes of bytes. - auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size)); - - // 7. Remove the first pullSize bytes from bytes. - if (pull_size != available) - bytes = MUST(bytes.slice(pull_size, available - pull_size)); - - // FIXME: 8. If stream’s current BYOB request view is non-null, then: - // 1. Write pulled into stream’s current BYOB request view. - // 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize). - // 9. Otherwise, - { - auto& realm = HTML::relevant_realm(stream); - - // 1. Set view to the result of creating a Uint8Array from pulled in stream’s relevant Realm. - auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled)); - auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); - - // 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view). - TRY(readable_byte_stream_controller_enqueue(controller, view)); - } - - return {}; -} - // https://streams.spec.whatwg.org/#transfer-array-buffer WebIDL::ExceptionOr> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer) { @@ -3642,52 +3566,6 @@ PullIntoDescriptor readable_byte_stream_controller_shift_pending_pull_into(Reada return descriptor; } -// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support -void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream& stream, GC::Ptr pull_algorithm, GC::Ptr cancel_algorithm, double high_water_mark) -{ - auto& realm = stream.realm(); - - // 1. Let startAlgorithm be an algorithm that returns undefined. - auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr { return JS::js_undefined(); }); - - // 2. Let pullAlgorithmWrapper be an algorithm that runs these steps: - auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() { - // 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. - GC::Ptr result = nullptr; - if (pull_algorithm) - result = pull_algorithm->function()(); - - // 2. If result is a Promise, then return result. - if (result != nullptr) - return GC::Ref(*result); - - // 3. Return a promise resolved with undefined. - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps: - auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) { - // 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. - GC::Ptr result = nullptr; - if (cancel_algorithm) - result = cancel_algorithm->function()(c); - - // 2. If result is a Promise, then return result. - if (result != nullptr) - return GC::Ref(*result); - - // 3. Return a promise resolved with undefined. - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 4. Perform ! InitializeReadableStream(stream). - // 5. Let controller be a new ReadableByteStreamController. - auto controller = realm.create(realm); - - // 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined). - MUST(set_up_readable_byte_stream_controller(stream, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined())); -} - // https://streams.spec.whatwg.org/#writable-stream-abort GC::Ref writable_stream_abort(WritableStream& stream, JS::Value reason) { diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index 11d143558e748..dceac54b5c918 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -74,7 +74,6 @@ Optional readable_stream_default_controller_get_desired_size(ReadableStr bool readable_stream_default_controller_can_close_or_enqueue(ReadableStreamDefaultController&); WebIDL::ExceptionOr set_up_readable_stream_default_controller(ReadableStream&, ReadableStreamDefaultController&, GC::Ref, GC::Ref, GC::Ref, double high_water_mark, GC::Ref); WebIDL::ExceptionOr set_up_readable_stream_default_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source_value, UnderlyingSource, double high_water_mark, GC::Ref); -void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream&, GC::Ptr = {}, GC::Ptr = {}, double high_water_mark = 0); WebIDL::ExceptionOr set_up_readable_byte_stream_controller(ReadableStream&, ReadableByteStreamController&, GC::Ref, GC::Ref, GC::Ref, double high_water_mark, JS::Value auto_allocate_chunk_size); WebIDL::ExceptionOr set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark); GC::Ptr readable_byte_stream_controller_get_byob_request(GC::Ref); @@ -85,9 +84,7 @@ WebIDL::ExceptionOr readable_byte_stream_controller_respond_internal(Reada WebIDL::ExceptionOr readable_byte_stream_controller_respond(ReadableByteStreamController&, u64 bytes_written); WebIDL::ExceptionOr readable_byte_stream_controller_respond_with_new_view(JS::Realm&, ReadableByteStreamController&, WebIDL::ArrayBufferView&); -WebIDL::ExceptionOr readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk); WebIDL::ExceptionOr readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk); -WebIDL::ExceptionOr readable_stream_pull_from_bytes(ReadableStream&, ByteBuffer bytes); WebIDL::ExceptionOr> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer); WebIDL::ExceptionOr readable_byte_stream_controller_enqueue_detached_pull_into_queue(ReadableByteStreamController& controller, PullIntoDescriptor& pull_into_descriptor); void readable_byte_stream_controller_commit_pull_into_descriptor(ReadableStream&, PullIntoDescriptor const&); diff --git a/Libraries/LibWeb/Streams/ReadableStream.cpp b/Libraries/LibWeb/Streams/ReadableStream.cpp index fc19a119d906a..035104793074c 100644 --- a/Libraries/LibWeb/Streams/ReadableStream.cpp +++ b/Libraries/LibWeb/Streams/ReadableStream.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -258,4 +259,128 @@ bool ReadableStream::is_disturbed() const return m_disturbed; } +// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes +WebIDL::ExceptionOr ReadableStream::pull_from_bytes(ByteBuffer bytes) +{ + auto& realm = this->realm(); + + // 1. Assert: stream.[[controller]] implements ReadableByteStreamController. + auto& controller = this->controller()->get>(); + + // 2. Let available be bytes’s length. + auto available = bytes.size(); + + // 3. Let desiredSize be available. + auto desired_size = available; + + // FIXME: 4. If stream’s current BYOB request view is non-null, then set desiredSize to stream’s current BYOB request + // view's byte length. + + // 5. Let pullSize be the smaller value of available and desiredSize. + auto pull_size = min(available, desired_size); + + // 6. Let pulled be the first pullSize bytes of bytes. + auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size)); + + // 7. Remove the first pullSize bytes from bytes. + if (pull_size != available) + bytes = MUST(bytes.slice(pull_size, available - pull_size)); + + // FIXME: 8. If stream’s current BYOB request view is non-null, then: + // 1. Write pulled into stream’s current BYOB request view. + // 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize). + // 9. Otherwise, + { + // 1. Set view to the result of creating a Uint8Array from pulled in stream’s relevant Realm. + auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled)); + auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer); + + // 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view). + TRY(readable_byte_stream_controller_enqueue(controller, view)); + } + + return {}; +} + +// https://streams.spec.whatwg.org/#readablestream-enqueue +WebIDL::ExceptionOr ReadableStream::enqueue(JS::Value chunk) +{ + VERIFY(m_controller.has_value()); + + // 1. If stream.[[controller]] implements ReadableStreamDefaultController, + if (m_controller->has>()) { + // 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk). + return readable_stream_default_controller_enqueue(m_controller->get>(), chunk); + } + // 2. Otherwise, + else { + // 1. Assert: stream.[[controller]] implements ReadableByteStreamController. + VERIFY(m_controller->has>()); + auto readable_byte_controller = m_controller->get>(); + + // FIXME: 2. Assert: chunk is an ArrayBufferView. + + // 3. Let byobView be the current BYOB request view for stream. + // FIXME: This is not what the spec means by 'current BYOB request view' + auto byob_view = readable_byte_controller->raw_byob_request(); + + // 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then: + if (byob_view) { + // FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]]. + // FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]]. + // FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]). + TODO(); + } + + // 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk). + return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk); + } +} + +// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support +void ReadableStream::set_up_with_byte_reading_support(GC::Ptr pull_algorithm, GC::Ptr cancel_algorithm, double high_water_mark) +{ + auto& realm = this->realm(); + + // 1. Let startAlgorithm be an algorithm that returns undefined. + auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr { return JS::js_undefined(); }); + + // 2. Let pullAlgorithmWrapper be an algorithm that runs these steps: + auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() { + // 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. + GC::Ptr result = nullptr; + if (pull_algorithm) + result = pull_algorithm->function()(); + + // 2. If result is a Promise, then return result. + if (result != nullptr) + return GC::Ref(*result); + + // 3. Return a promise resolved with undefined. + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps: + auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) { + // 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. + GC::Ptr result = nullptr; + if (cancel_algorithm) + result = cancel_algorithm->function()(c); + + // 2. If result is a Promise, then return result. + if (result != nullptr) + return GC::Ref(*result); + + // 3. Return a promise resolved with undefined. + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 4. Perform ! InitializeReadableStream(stream). + // 5. Let controller be a new ReadableByteStreamController. + auto controller = realm.create(realm); + + // 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined). + MUST(set_up_readable_byte_stream_controller(*this, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined())); +} + } diff --git a/Libraries/LibWeb/Streams/ReadableStream.h b/Libraries/LibWeb/Streams/ReadableStream.h index e260ef31393c0..29d3418cc30e7 100644 --- a/Libraries/LibWeb/Streams/ReadableStream.h +++ b/Libraries/LibWeb/Streams/ReadableStream.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::Streams { @@ -104,6 +105,10 @@ class ReadableStream final : public Bindings::PlatformObject { State state() const { return m_state; } void set_state(State value) { m_state = value; } + WebIDL::ExceptionOr pull_from_bytes(ByteBuffer); + WebIDL::ExceptionOr enqueue(JS::Value chunk); + void set_up_with_byte_reading_support(GC::Ptr = {}, GC::Ptr = {}, double high_water_mark = 0); + private: explicit ReadableStream(JS::Realm&); From 19bbfb023ab0d94daef30a2c04d7b7ab1011d317 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 8 Dec 2024 17:35:07 +1300 Subject: [PATCH 059/237] LibWeb/Streams: Move "set up transform stream" to TransformStream This is not marked as an AO in the spec, and is a publically exported API exposed on TransformStream. --- .../LibWeb/Compression/CompressionStream.cpp | 2 +- .../Compression/DecompressionStream.cpp | 2 +- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 2 +- .../LibWeb/Streams/AbstractOperations.cpp | 78 ------------------- Libraries/LibWeb/Streams/AbstractOperations.h | 1 - Libraries/LibWeb/Streams/TransformStream.cpp | 78 +++++++++++++++++++ Libraries/LibWeb/Streams/TransformStream.h | 3 + 7 files changed, 84 insertions(+), 82 deletions(-) diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index b72bbd8c99763..831bc9d67040c 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -76,7 +76,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp }); // 6. Set up this's transform with transformAlgorithm set to transformAlgorithm and flushAlgorithm set to flushAlgorithm. - Streams::transform_stream_set_up(stream->m_transform, transform_algorithm, flush_algorithm); + stream->m_transform->set_up(transform_algorithm, flush_algorithm); return stream; } diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index 1b2b909a8a3f0..99bae93b15509 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -77,7 +77,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct }); // 6. Set up this's transform with transformAlgorithm set to transformAlgorithm and flushAlgorithm set to flushAlgorithm. - Streams::transform_stream_set_up(stream->m_transform, transform_algorithm, flush_algorithm); + stream->m_transform->set_up(transform_algorithm, flush_algorithm); return stream; } diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 207f0ad619ed4..a576937d1dda7 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -741,7 +741,7 @@ void fetch_response_handover(JS::Realm& realm, Infrastructure::FetchParams const process_response_end_of_body(); return WebIDL::create_resolved_promise(realm, JS::js_undefined()); }); - Streams::transform_stream_set_up(transform_stream, identity_transform_algorithm, flush_algorithm); + transform_stream->set_up(identity_transform_algorithm, flush_algorithm); // 4. Set internalResponse’s body’s stream to the result of internalResponse’s body’s stream piped through transformStream. auto promise = Streams::readable_stream_pipe_to(internal_response->body()->stream(), transform_stream->writable(), false, false, false, {}); diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index b34f2c16208a4..a77b5fd729305 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -5176,84 +5176,6 @@ void transform_stream_set_backpressure(TransformStream& stream, bool backpressur stream.set_backpressure(backpressure); } -// https://streams.spec.whatwg.org/#transformstream-set-up -void transform_stream_set_up(TransformStream& stream, GC::Ref transform_algorithm, GC::Ptr flush_algorithm, GC::Ptr cancel_algorithm) -{ - auto& realm = stream.realm(); - - // 1. Let writableHighWaterMark be 1. - auto writable_high_water_mark = 1.0; - - // 2. Let writableSizeAlgorithm be an algorithm that returns 1. - auto writable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { - return JS::normal_completion(JS::Value { 1 }); - }); - - // 3. Let readableHighWaterMark be 0. - auto readable_high_water_mark = 0.0; - - // 4. Let readableSizeAlgorithm be an algorithm that returns 1. - auto readable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { - return JS::normal_completion(JS::Value { 1 }); - }); - - // 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk: - auto transform_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, transform_algorithm](JS::Value chunk) -> GC::Ref { - // 1. Let result be the result of running transformAlgorithm given chunk. If this throws an exception e, return a promise rejected with e. - GC::Ptr result = nullptr; - result = transform_algorithm->function()(chunk); - - // 2. If result is a Promise, then return result. - if (result) - return GC::Ref { *result }; - - // 3. Return a promise resolved with undefined. - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 6. Let flushAlgorithmWrapper be an algorithm that runs these steps: - auto flush_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, flush_algorithm]() -> GC::Ref { - // 1. Let result be the result of running flushAlgorithm, if flushAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. - GC::Ptr result = nullptr; - if (flush_algorithm) - result = flush_algorithm->function()(); - - // 2. If result is a Promise, then return result. - if (result) - return GC::Ref { *result }; - - // 3. Return a promise resolved with undefined. - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason: - auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value reason) -> GC::Ref { - // 1. Let result be the result of running cancelAlgorithm given reason, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. - GC::Ptr result = nullptr; - if (cancel_algorithm) - result = cancel_algorithm->function()(reason); - - // 2. If result is a Promise, then return result. - if (result) - return GC::Ref { *result }; - - // 3. Return a promise resolved with undefined. - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 8. Let startPromise be a promise resolved with undefined. - auto start_promise = WebIDL::create_resolved_promise(realm, JS::js_undefined()); - - // 9. Perform ! InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm). - initialize_transform_stream(stream, start_promise, writable_high_water_mark, writable_size_algorithm, readable_high_water_mark, readable_size_algorithm); - - // 10. Let controller be a new TransformStreamDefaultController. - auto controller = realm.create(realm); - - // 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper). - set_up_transform_stream_default_controller(stream, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper); -} - // https://streams.spec.whatwg.org/#transform-stream-unblock-write void transform_stream_unblock_write(TransformStream& stream) { diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index dceac54b5c918..7229f55498323 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -172,7 +172,6 @@ GC::Ref transform_stream_default_source_cancel_algorithm(Transf void transform_stream_error(TransformStream&, JS::Value error); void transform_stream_error_writable_and_unblock_write(TransformStream&, JS::Value error); void transform_stream_set_backpressure(TransformStream&, bool backpressure); -void transform_stream_set_up(TransformStream&, GC::Ref, GC::Ptr = {}, GC::Ptr = {}); void transform_stream_unblock_write(TransformStream&); bool is_non_negative_number(JS::Value); diff --git a/Libraries/LibWeb/Streams/TransformStream.cpp b/Libraries/LibWeb/Streams/TransformStream.cpp index 2387e6bc3f28d..125ff3acae950 100644 --- a/Libraries/LibWeb/Streams/TransformStream.cpp +++ b/Libraries/LibWeb/Streams/TransformStream.cpp @@ -74,6 +74,84 @@ WebIDL::ExceptionOr> TransformStream::construct_impl(JS return stream; } +// https://streams.spec.whatwg.org/#transformstream-set-up +void TransformStream::set_up(GC::Ref transform_algorithm, GC::Ptr flush_algorithm, GC::Ptr cancel_algorithm) +{ + auto& realm = this->realm(); + + // 1. Let writableHighWaterMark be 1. + auto writable_high_water_mark = 1.0; + + // 2. Let writableSizeAlgorithm be an algorithm that returns 1. + auto writable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { + return JS::normal_completion(JS::Value { 1 }); + }); + + // 3. Let readableHighWaterMark be 0. + auto readable_high_water_mark = 0.0; + + // 4. Let readableSizeAlgorithm be an algorithm that returns 1. + auto readable_size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { + return JS::normal_completion(JS::Value { 1 }); + }); + + // 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk: + auto transform_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, transform_algorithm](JS::Value chunk) -> GC::Ref { + // 1. Let result be the result of running transformAlgorithm given chunk. If this throws an exception e, return a promise rejected with e. + GC::Ptr result = nullptr; + result = transform_algorithm->function()(chunk); + + // 2. If result is a Promise, then return result. + if (result) + return GC::Ref { *result }; + + // 3. Return a promise resolved with undefined. + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 6. Let flushAlgorithmWrapper be an algorithm that runs these steps: + auto flush_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, flush_algorithm]() -> GC::Ref { + // 1. Let result be the result of running flushAlgorithm, if flushAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. + GC::Ptr result = nullptr; + if (flush_algorithm) + result = flush_algorithm->function()(); + + // 2. If result is a Promise, then return result. + if (result) + return GC::Ref { *result }; + + // 3. Return a promise resolved with undefined. + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason: + auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value reason) -> GC::Ref { + // 1. Let result be the result of running cancelAlgorithm given reason, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e. + GC::Ptr result = nullptr; + if (cancel_algorithm) + result = cancel_algorithm->function()(reason); + + // 2. If result is a Promise, then return result. + if (result) + return GC::Ref { *result }; + + // 3. Return a promise resolved with undefined. + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 8. Let startPromise be a promise resolved with undefined. + auto start_promise = WebIDL::create_resolved_promise(realm, JS::js_undefined()); + + // 9. Perform ! InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + initialize_transform_stream(*this, start_promise, writable_high_water_mark, writable_size_algorithm, readable_high_water_mark, readable_size_algorithm); + + // 10. Let controller be a new TransformStreamDefaultController. + auto controller = realm.create(realm); + + // 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper). + set_up_transform_stream_default_controller(*this, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper); +} + TransformStream::TransformStream(JS::Realm& realm) : Bindings::PlatformObject(realm) { diff --git a/Libraries/LibWeb/Streams/TransformStream.h b/Libraries/LibWeb/Streams/TransformStream.h index a1ba305222fc8..753ff12eafe68 100644 --- a/Libraries/LibWeb/Streams/TransformStream.h +++ b/Libraries/LibWeb/Streams/TransformStream.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,8 @@ class TransformStream final : public Bindings::PlatformObject { GC::Ptr controller() const { return m_controller; } void set_controller(GC::Ptr value) { m_controller = value; } + void set_up(GC::Ref, GC::Ptr = {}, GC::Ptr = {}); + private: explicit TransformStream(JS::Realm& realm); From 93f258deb7672a9e72a0a3d63d25cdf9c164c35f Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 8 Dec 2024 17:39:04 +1300 Subject: [PATCH 060/237] LibWeb/Streams: Do not expose some non-standard functions in header These are non-standard and only needed internally as implementation details in the implementation of AbstractOperations, so let's keep them at a file-local level. --- .../LibWeb/Streams/AbstractOperations.cpp | 94 +++++++++---------- Libraries/LibWeb/Streams/AbstractOperations.h | 3 - 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index a77b5fd729305..539dd74532474 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -45,6 +45,53 @@ namespace Web::Streams { +// https://streams.spec.whatwg.org/#close-sentinel +// Non-standard function that implements the "close sentinel" value. +static JS::Value create_close_sentinel() +{ + // The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers. + // Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers. + return {}; +} + +// https://streams.spec.whatwg.org/#close-sentinel +// Non-standard function that implements the "If value is a close sentinel" check. +static bool is_close_sentinel(JS::Value value) +{ + return value.is_empty(); +} + +// NON-STANDARD: Can be used instead of CreateReadableStream in cases where we need to set up a newly allocated +// ReadableStream before initialization of said ReadableStream, i.e. ReadableStream is captured by lambdas in an uninitialized state. +// Spec steps are taken from: https://streams.spec.whatwg.org/#create-readable-stream +static WebIDL::ExceptionOr set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm, Optional high_water_mark = {}, GC::Ptr size_algorithm = {}) +{ + // 1. If highWaterMark was not passed, set it to 1. + if (!high_water_mark.has_value()) + high_water_mark = 1.0; + + // 2. If sizeAlgorithm was not passed, set it to an algorithm that returns 1. + if (!size_algorithm) + size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { return JS::normal_completion(JS::Value(1)); }); + + // 3. Assert: ! IsNonNegativeNumber(highWaterMark) is true. + VERIFY(is_non_negative_number(JS::Value { *high_water_mark })); + + // 4. Let stream be a new ReadableStream. + // NOTE: The ReadableStream is allocated outside the scope of this method. + + // 5. Perform ! InitializeReadableStream(stream). + initialize_readable_stream(stream); + + // 6. Let controller be a new ReadableStreamDefaultController. + auto controller = realm.create(realm); + + // 7. Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm). + TRY(set_up_readable_stream_default_controller(stream, *controller, start_algorithm, pull_algorithm, cancel_algorithm, *high_water_mark, *size_algorithm)); + + return {}; +} + // https://streams.spec.whatwg.org/#acquire-readable-stream-reader WebIDL::ExceptionOr> acquire_readable_stream_default_reader(ReadableStream& stream) { @@ -2932,37 +2979,6 @@ bool readable_byte_stream_controller_should_call_pull(ReadableByteStreamControll return false; } -// NON-STANDARD: Can be used instead of CreateReadableStream in cases where we need to set up a newly allocated -// ReadableStream before initialization of said ReadableStream, i.e. ReadableStream is captured by lambdas in an uninitialized state. -// Spec steps are taken from: https://streams.spec.whatwg.org/#create-readable-stream -WebIDL::ExceptionOr set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm, Optional high_water_mark, GC::Ptr size_algorithm) -{ - // 1. If highWaterMark was not passed, set it to 1. - if (!high_water_mark.has_value()) - high_water_mark = 1.0; - - // 2. If sizeAlgorithm was not passed, set it to an algorithm that returns 1. - if (!size_algorithm) - size_algorithm = GC::create_function(realm.heap(), [](JS::Value) { return JS::normal_completion(JS::Value(1)); }); - - // 3. Assert: ! IsNonNegativeNumber(highWaterMark) is true. - VERIFY(is_non_negative_number(JS::Value { *high_water_mark })); - - // 4. Let stream be a new ReadableStream. - // NOTE: The ReadableStream is allocated outside the scope of this method. - - // 5. Perform ! InitializeReadableStream(stream). - initialize_readable_stream(stream); - - // 6. Let controller be a new ReadableStreamDefaultController. - auto controller = realm.create(realm); - - // 7. Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm). - TRY(set_up_readable_stream_default_controller(stream, *controller, start_algorithm, pull_algorithm, cancel_algorithm, *high_water_mark, *size_algorithm)); - - return {}; -} - // https://streams.spec.whatwg.org/#create-readable-stream WebIDL::ExceptionOr> create_readable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm, Optional high_water_mark, GC::Ptr size_algorithm) { @@ -5286,22 +5302,6 @@ WebIDL::ExceptionOr structured_clone(JS::Realm& realm, JS::Value valu return TRY(HTML::structured_deserialize(vm, serialized, realm)); } -// https://streams.spec.whatwg.org/#close-sentinel -// Non-standard function that implements the "close sentinel" value. -JS::Value create_close_sentinel() -{ - // The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers. - // Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers. - return {}; -} - -// https://streams.spec.whatwg.org/#close-sentinel -// Non-standard function that implements the "If value is a close sentinel" check. -bool is_close_sentinel(JS::Value value) -{ - return value.is_empty(); -} - // Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially // what the Bindings generator would do at compile time, but at runtime instead. JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise operation_returns_promise) diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index 7229f55498323..ef8a16808ef76 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -106,7 +106,6 @@ void readable_byte_stream_controller_handle_queue_drain(ReadableByteStreamContro void readable_byte_stream_controller_invalidate_byob_request(ReadableByteStreamController&); bool readable_byte_stream_controller_should_call_pull(ReadableByteStreamController const&); -WebIDL::ExceptionOr set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm, Optional high_water_mark = {}, GC::Ptr size_algorithm = {}); WebIDL::ExceptionOr> create_readable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm, Optional high_water_mark = {}, GC::Ptr size_algorithm = {}); WebIDL::ExceptionOr> create_readable_byte_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref pull_algorithm, GC::Ref cancel_algorithm); WebIDL::ExceptionOr> create_writable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm); @@ -180,8 +179,6 @@ bool can_transfer_array_buffer(JS::ArrayBuffer const& array_buffer); WebIDL::ExceptionOr clone_as_uint8_array(JS::Realm&, WebIDL::ArrayBufferView&); WebIDL::ExceptionOr structured_clone(JS::Realm&, JS::Value value); -JS::Value create_close_sentinel(); -bool is_close_sentinel(JS::Value); JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise); // https://streams.spec.whatwg.org/#value-with-size From 99073c0561d589e463aab43807310a4db394ecf1 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 8 Dec 2024 17:43:24 +1300 Subject: [PATCH 061/237] LibWeb: Move ad hoc CallbackType helper method to CallbackType header Abstract operations of a stream does not seem like the correct home for this function. --- Libraries/LibWeb/Streams/AbstractOperations.cpp | 15 --------------- Libraries/LibWeb/Streams/AbstractOperations.h | 2 -- Libraries/LibWeb/Streams/Transformer.cpp | 8 ++++---- Libraries/LibWeb/Streams/UnderlyingSink.cpp | 8 ++++---- Libraries/LibWeb/Streams/UnderlyingSource.cpp | 6 +++--- Libraries/LibWeb/WebIDL/CallbackType.cpp | 16 ++++++++++++++++ Libraries/LibWeb/WebIDL/CallbackType.h | 2 ++ 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 539dd74532474..103ae128b104b 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -5302,21 +5302,6 @@ WebIDL::ExceptionOr structured_clone(JS::Realm& realm, JS::Value valu return TRY(HTML::structured_deserialize(vm, serialized, realm)); } -// Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially -// what the Bindings generator would do at compile time, but at runtime instead. -JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise operation_returns_promise) -{ - auto property = TRY(value.get(vm, property_key)); - - if (property.is_undefined()) - return GC::Root {}; - - if (!property.is_function()) - return vm.throw_completion(JS::ErrorType::NotAFunction, property.to_string_without_side_effects()); - - return vm.heap().allocate(property.as_object(), HTML::incumbent_realm(), operation_returns_promise); -} - // https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source WebIDL::ExceptionOr set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream& stream, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark) { diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index ef8a16808ef76..ec181944ffd65 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -179,8 +179,6 @@ bool can_transfer_array_buffer(JS::ArrayBuffer const& array_buffer); WebIDL::ExceptionOr clone_as_uint8_array(JS::Realm&, WebIDL::ArrayBufferView&); WebIDL::ExceptionOr structured_clone(JS::Realm&, JS::Value value); -JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise); - // https://streams.spec.whatwg.org/#value-with-size struct ValueWithSize { JS::Value value; diff --git a/Libraries/LibWeb/Streams/Transformer.cpp b/Libraries/LibWeb/Streams/Transformer.cpp index 1d07ad4cbe667..3eaedc2e75511 100644 --- a/Libraries/LibWeb/Streams/Transformer.cpp +++ b/Libraries/LibWeb/Streams/Transformer.cpp @@ -19,10 +19,10 @@ JS::ThrowCompletionOr Transformer::from_value(JS::VM& vm, JS::Value auto& object = value.as_object(); Transformer transformer { - .start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), - .transform = TRY(property_to_callback(vm, value, "transform", WebIDL::OperationReturnsPromise::Yes)), - .flush = TRY(property_to_callback(vm, value, "flush", WebIDL::OperationReturnsPromise::Yes)), - .cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)), + .start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), + .transform = TRY(WebIDL::property_to_callback(vm, value, "transform", WebIDL::OperationReturnsPromise::Yes)), + .flush = TRY(WebIDL::property_to_callback(vm, value, "flush", WebIDL::OperationReturnsPromise::Yes)), + .cancel = TRY(WebIDL::property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)), .readable_type = {}, .writable_type = {}, }; diff --git a/Libraries/LibWeb/Streams/UnderlyingSink.cpp b/Libraries/LibWeb/Streams/UnderlyingSink.cpp index 553b7a986192b..cf036668f72d1 100644 --- a/Libraries/LibWeb/Streams/UnderlyingSink.cpp +++ b/Libraries/LibWeb/Streams/UnderlyingSink.cpp @@ -19,10 +19,10 @@ JS::ThrowCompletionOr UnderlyingSink::from_value(JS::VM& vm, JS: auto& object = value.as_object(); UnderlyingSink underlying_sink { - .start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), - .write = TRY(property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)), - .close = TRY(property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)), - .abort = TRY(property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)), + .start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), + .write = TRY(WebIDL::property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)), + .close = TRY(WebIDL::property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)), + .abort = TRY(WebIDL::property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)), .type = {}, }; diff --git a/Libraries/LibWeb/Streams/UnderlyingSource.cpp b/Libraries/LibWeb/Streams/UnderlyingSource.cpp index b2d92f6cb90e7..3c1cf53111621 100644 --- a/Libraries/LibWeb/Streams/UnderlyingSource.cpp +++ b/Libraries/LibWeb/Streams/UnderlyingSource.cpp @@ -22,9 +22,9 @@ JS::ThrowCompletionOr UnderlyingSource::from_value(JS::VM& vm, auto& object = value.as_object(); UnderlyingSource underlying_source { - .start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), - .pull = TRY(property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)), - .cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)), + .start = TRY(WebIDL::property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), + .pull = TRY(WebIDL::property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)), + .cancel = TRY(WebIDL::property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)), .type = {}, .auto_allocate_chunk_size = {}, }; diff --git a/Libraries/LibWeb/WebIDL/CallbackType.cpp b/Libraries/LibWeb/WebIDL/CallbackType.cpp index 24dd1fc3cdcb9..8a363520eedf2 100644 --- a/Libraries/LibWeb/WebIDL/CallbackType.cpp +++ b/Libraries/LibWeb/WebIDL/CallbackType.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -26,4 +27,19 @@ void CallbackType::visit_edges(Cell::Visitor& visitor) visitor.visit(callback_context); } +// Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially +// what the Bindings generator would do at compile time, but at runtime instead. +JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, OperationReturnsPromise operation_returns_promise) +{ + auto property = TRY(value.get(vm, property_key)); + + if (property.is_undefined()) + return GC::Root {}; + + if (!property.is_function()) + return vm.throw_completion(JS::ErrorType::NotAFunction, property.to_string_without_side_effects()); + + return vm.heap().allocate(property.as_object(), HTML::incumbent_realm(), operation_returns_promise); +} + } diff --git a/Libraries/LibWeb/WebIDL/CallbackType.h b/Libraries/LibWeb/WebIDL/CallbackType.h index f8ca51b52692d..125e13110e77f 100644 --- a/Libraries/LibWeb/WebIDL/CallbackType.h +++ b/Libraries/LibWeb/WebIDL/CallbackType.h @@ -39,4 +39,6 @@ class CallbackType final : public JS::Cell { virtual void visit_edges(Cell::Visitor&) override; }; +JS::ThrowCompletionOr> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise); + } From b9ac4557d697d0743d631912a58de8a4b0e5c749 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 09:23:29 -0500 Subject: [PATCH 062/237] LibJS: Change variable name of parsed time zone offset This is an editorial change in the Temporal proposal. See: https://github.com/tc39/proposal-temporal/commit/30d17d3 --- Libraries/LibJS/Runtime/Temporal/TimeZone.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index a2fe6c8f38653..32e43461dd725 100644 --- a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -458,8 +458,8 @@ TimeZone parse_time_zone_identifier(ParseResult const& parse_result) // a. Assert: parseResult contains a UTCOffset[~SubMinutePrecision] Parse Node. VERIFY(parse_result.time_zone_offset.has_value()); - // b. Let offsetString be the source text matched by the UTCOffset[~SubMinutePrecision] Parse Node contained within parseResult. - // c. Let offsetNanoseconds be ! ParseDateTimeUTCOffset(offsetString). + // b. Let offset be the source text matched by the UTCOffset[~SubMinutePrecision] Parse Node contained within parseResult. + // c. Let offsetNanoseconds be ! ParseDateTimeUTCOffset(CodePointsToString(offset)). auto offset_nanoseconds = parse_date_time_utc_offset(parse_result.time_zone_offset->source_text); // d. Let offsetMinutes be offsetNanoseconds / (60 × 10**9). From 002b0ea7c78a1972a1612c4e786a8aa01dc49043 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 09:26:38 -0500 Subject: [PATCH 063/237] LibJS: Remove dead code from ZonedDateTime.prototype.toLocaleString This is an editorial change in the Temporal proposal. See: https://github.com/tc39/proposal-temporal/commit/065cf94 --- .../Temporal/ZonedDateTimePrototype.cpp | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp b/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp index f9d986cfc9aad..b8a6ef8266b97 100644 --- a/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp +++ b/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp @@ -800,43 +800,19 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::to_locale_string) // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). auto zoned_date_time = TRY(typed_this_object(vm)); - // 3. Let timeZone be zonedDateTime.[[TimeZone]]. - auto time_zone = zoned_date_time->time_zone(); - - // 4. Let timeZoneParseResult be ? ParseTimeZoneIdentifier(timeZone). - auto time_zone_parse_result = TRY(parse_time_zone_identifier(vm, time_zone)); - - // 5. If timeZoneParseResult.[[OffsetMinutes]] is empty, then - if (!time_zone_parse_result.offset_minutes.has_value()) { - // a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone). - auto time_zone_identifier_record = Intl::get_available_named_time_zone_identifier(time_zone); - - // b. If timeZoneIdentifierRecord is empty, throw a RangeError exception. - if (!time_zone_identifier_record.has_value()) - return vm.throw_completion(ErrorType::TemporalInvalidTimeZoneName, time_zone); - - // c. Set timeZone to timeZoneIdentifierRecord.[[Identifier]]. - time_zone = time_zone_identifier_record->identifier; - } - // 6. Else, - else { - // a. Set timeZone to FormatOffsetTimeZoneIdentifier(timeZoneParseResult.[[OffsetMinutes]]). - time_zone = format_offset_time_zone_identifier(*time_zone_parse_result.offset_minutes); - } - - // 7. Let dateTimeFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, ANY, ALL, timeZone). - auto date_time_format = TRY(Intl::create_date_time_format(vm, realm.intrinsics().intl_date_time_format_constructor(), locales, options, Intl::OptionRequired::Any, Intl::OptionDefaults::All, time_zone)); + // 3. Let dateTimeFormat be ? CreateDateTimeFormat(%Intl.DateTimeFormat%, locales, options, ANY, ALL, zonedDateTime.[[TimeZone]]). + auto date_time_format = TRY(Intl::create_date_time_format(vm, realm.intrinsics().intl_date_time_format_constructor(), locales, options, Intl::OptionRequired::Any, Intl::OptionDefaults::All, zoned_date_time->time_zone())); - // 8. If zonedDateTime.[[Calendar]] is not "iso8601" and CalendarEquals(zonedDateTime.[[Calendar]], dateTimeFormat.[[Calendar]]) is false, then + // 4. If zonedDateTime.[[Calendar]] is not "iso8601" and CalendarEquals(zonedDateTime.[[Calendar]], dateTimeFormat.[[Calendar]]) is false, then if (zoned_date_time->calendar() != "iso8601"sv && !calendar_equals(zoned_date_time->calendar(), date_time_format->calendar())) { // a. Throw a RangeError exception. return vm.throw_completion(ErrorType::IntlTemporalInvalidCalendar, "Temporal.ZonedDateTime"sv, zoned_date_time->calendar(), date_time_format->calendar()); } - // 9. Let instant be ! CreateTemporalInstant(zonedDateTime.[[EpochNanoseconds]]). + // 5. Let instant be ! CreateTemporalInstant(zonedDateTime.[[EpochNanoseconds]]). auto instant = MUST(create_temporal_instant(vm, zoned_date_time->epoch_nanoseconds())); - // 10. Return ? FormatDateTime(dateTimeFormat, instant). + // 6. Return ? FormatDateTime(dateTimeFormat, instant). return PrimitiveString::create(vm, TRY(Intl::format_date_time(vm, date_time_format, instant))); } From 0bc55424c8d9297d278bc97f864f8c3c9cf0220c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 09:29:07 -0500 Subject: [PATCH 064/237] LibJS: Fix variable reference in CreateDateTimeFormat This is an editorial change in the Temporal proposal. See: https://github.com/tc39/proposal-temporal/commit/8acd353 --- .../LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp index 0cd116c498fd1..c8a33df77977f 100644 --- a/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DateTimeFormatConstructor.cpp @@ -367,15 +367,15 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct // f. If dateStyle is not undefined, then if (!date_style.is_undefined()) { - // i. Set dateTimeFormat.[[TemporalPlainDateFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]] »). + // i. Set dateTimeFormat.[[TemporalPlainDateFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, formatMatcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]] »). auto temporal_plain_date_format = adjust_date_time_style_format(best_format, { { Weekday, Era, Year, Month, Day } }); date_time_format->set_temporal_plain_date_format(move(temporal_plain_date_format)); - // ii. Set dateTimeFormat.[[TemporalPlainYearMonthFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[era]], [[year]], [[month]] »). + // ii. Set dateTimeFormat.[[TemporalPlainYearMonthFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, formatMatcher, « [[era]], [[year]], [[month]] »). auto temporal_plain_year_month_format = adjust_date_time_style_format(best_format, { { Era, Year, Month } }); date_time_format->set_temporal_plain_year_month_format(move(temporal_plain_year_month_format)); - // iii. Set dateTimeFormat.[[TemporalPlainMonthDayFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[month]], [[day]] »). + // iii. Set dateTimeFormat.[[TemporalPlainMonthDayFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, formatMatcher, « [[month]], [[day]] »). auto temporal_plain_month_day_format = adjust_date_time_style_format(best_format, { { Month, Day } }); date_time_format->set_temporal_plain_month_day_format(move(temporal_plain_month_day_format)); } @@ -388,7 +388,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct // h. If timeStyle is not undefined, then if (!time_style.is_undefined()) { - // i. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »). + // i. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, formatMatcher, « [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »). auto temporal_plain_time_format = adjust_date_time_style_format(best_format, { { DayPeriod, Hour, Minute, Second, FractionalSecondDigits } }); date_time_format->set_temporal_plain_time_format(move(temporal_plain_time_format)); } @@ -397,7 +397,7 @@ ThrowCompletionOr> create_date_time_format(VM& vm, Funct // i. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to null. } - // j. Set dateTimeFormat.[[TemporalPlainDateTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]], [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »). + // j. Set dateTimeFormat.[[TemporalPlainDateTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, formatMatcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]], [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »). auto temporal_plain_date_time_format = adjust_date_time_style_format(best_format, { { Weekday, Era, Year, Month, Day, DayPeriod, Hour, Minute, Second, FractionalSecondDigits } }); date_time_format->set_temporal_plain_date_time_format(move(temporal_plain_date_time_format)); From 943ec820fc52ea82f64654c1fe7e21d6e08e6e63 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 12:04:02 -0500 Subject: [PATCH 065/237] LibWeb: Avoid dereferencing an empty optional URL Here, "null" means the empty optional. We don't need to also check if the URL is valid; the url will be null if it was originally invalid. --- Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp | 2 +- Tests/LibWeb/Text/expected/HTML/href-invalid.txt | 1 + Tests/LibWeb/Text/input/HTML/href-invalid.html | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/href-invalid.txt create mode 100644 Tests/LibWeb/Text/input/HTML/href-invalid.html diff --git a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp index eb0698d2bfeb2..6f49f0fb6d120 100644 --- a/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp +++ b/Libraries/LibWeb/HTML/HTMLHyperlinkElementUtils.cpp @@ -427,7 +427,7 @@ String HTMLHyperlinkElementUtils::href() const return String {}; // 4. Otherwise, if url is null, return this element's href content attribute's value. - if (!url->is_valid()) + if (!url.has_value()) return href_content_attribute.release_value(); // 5. Return url, serialized. diff --git a/Tests/LibWeb/Text/expected/HTML/href-invalid.txt b/Tests/LibWeb/Text/expected/HTML/href-invalid.txt new file mode 100644 index 0000000000000..383281360f510 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/href-invalid.txt @@ -0,0 +1 @@ +href="http://foo:b/c" diff --git a/Tests/LibWeb/Text/input/HTML/href-invalid.html b/Tests/LibWeb/Text/input/HTML/href-invalid.html new file mode 100644 index 0000000000000..b4ec10d0feb99 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/href-invalid.html @@ -0,0 +1,8 @@ + + From d14fd8a6c88688349b015e5cafa4d7055bc34ad1 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Wed, 11 Dec 2024 16:43:01 +0100 Subject: [PATCH 066/237] LibWeb: Update spec steps in Selection Resolves two FIXMEs in `::collapse()` and `::select_all_children()`. --- Libraries/LibWeb/Selection/Selection.cpp | 37 ++++++++++-------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/Libraries/LibWeb/Selection/Selection.cpp b/Libraries/LibWeb/Selection/Selection.cpp index 3ff7b6edaf07c..3577056a07d4f 100644 --- a/Libraries/LibWeb/Selection/Selection.cpp +++ b/Libraries/LibWeb/Selection/Selection.cpp @@ -196,28 +196,25 @@ WebIDL::ExceptionOr Selection::collapse(GC::Ptr node, unsigned return {}; } - // FIXME: Update this to match the spec once the spec is updated. - // Spec PR: https://github.com/w3c/selection-api/pull/342 - if (node->is_document_type()) { + // 2. If node is a DocumentType, throw an InvalidNodeTypeError exception and abort these steps. + if (node->is_document_type()) return WebIDL::InvalidNodeTypeError::create(realm(), "Selection.collapse() with DocumentType node"_string); - } - // 2. The method must throw an IndexSizeError exception if offset is longer than node's length and abort these steps. - if (offset > node->length()) { + // 3. The method must throw an IndexSizeError exception if offset is longer than node's length and abort these steps. + if (offset > node->length()) return WebIDL::IndexSizeError::create(realm(), "Selection.collapse() with offset longer than node's length"_string); - } - // 3. If document associated with this is not a shadow-including inclusive ancestor of node, abort these steps. + // 4. If document associated with this is not a shadow-including inclusive ancestor of node, abort these steps. if (!m_document->is_shadow_including_inclusive_ancestor_of(*node)) return {}; - // 4. Otherwise, let newRange be a new range. + // 5. Otherwise, let newRange be a new range. auto new_range = DOM::Range::create(*m_document); - // 5. Set the start the start and the end of newRange to (node, offset). + // 6. Set the start the start and the end of newRange to (node, offset). TRY(new_range->set_start(*node, offset)); - // 6. Set this's range to newRange. + // 7. Set this's range to newRange. set_range(new_range); return {}; @@ -367,30 +364,28 @@ WebIDL::ExceptionOr Selection::set_base_and_extent(GC::Ref anch // https://w3c.github.io/selection-api/#dom-selection-selectallchildren WebIDL::ExceptionOr Selection::select_all_children(GC::Ref node) { - // FIXME: Update this to match the spec once the spec is updated. - // Spec PR: https://github.com/w3c/selection-api/pull/342 - if (node->is_document_type()) { + // 1. If node is a DocumentType, throw an InvalidNodeTypeError exception and abort these steps. + if (node->is_document_type()) return WebIDL::InvalidNodeTypeError::create(realm(), "Selection.selectAllChildren() with DocumentType node"_string); - } - // 1. If node's root is not the document associated with this, abort these steps. + // 2. If node's root is not the document associated with this, abort these steps. if (&node->root() != m_document.ptr()) return {}; - // 2. Let newRange be a new range and childCount be the number of children of node. + // 3. Let newRange be a new range and childCount be the number of children of node. auto new_range = DOM::Range::create(*m_document); auto child_count = node->child_count(); - // 3. Set newRange's start to (node, 0). + // 4. Set newRange's start to (node, 0). TRY(new_range->set_start(node, 0)); - // 4. Set newRange's end to (node, childCount). + // 5. Set newRange's end to (node, childCount). TRY(new_range->set_end(node, child_count)); - // 5. Set this's range to newRange. + // 6. Set this's range to newRange. set_range(new_range); - // 6. Set this's direction to forwards. + // 7. Set this's direction to forwards. m_direction = Direction::Forwards; return {}; From e7ef8da7f39b8381f6ddb73aeffbcc2952791e71 Mon Sep 17 00:00:00 2001 From: Simek Date: Wed, 11 Dec 2024 17:46:13 +0100 Subject: [PATCH 067/237] LibWeb/ARIA: Add missing structure roles --- Libraries/LibWeb/ARIA/Roles.cpp | 2 + .../wpt-import/wai-aria/role/roles.txt | 60 ++++++++ .../input/wpt-import/wai-aria/role/roles.html | 140 ++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/wai-aria/role/roles.html diff --git a/Libraries/LibWeb/ARIA/Roles.cpp b/Libraries/LibWeb/ARIA/Roles.cpp index aa2e182905049..4030acd960dde 100644 --- a/Libraries/LibWeb/ARIA/Roles.cpp +++ b/Libraries/LibWeb/ARIA/Roles.cpp @@ -103,6 +103,7 @@ bool is_document_structure_role(Role role) Role::blockquote, Role::caption, Role::cell, + Role::code, Role::columnheader, Role::definition, Role::deletion, @@ -130,6 +131,7 @@ bool is_document_structure_role(Role role) Role::separator, // TODO: Only when not focusable Role::strong, Role::subscript, + Role::superscript, Role::table, Role::term, Role::time, diff --git a/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt new file mode 100644 index 0000000000000..cbed5d0d60e17 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt @@ -0,0 +1,60 @@ +Harness status: OK + +Found 54 tests + +53 Pass +1 Fail +Pass role: alert +Pass role: alertdialog +Pass role: application +Pass role: article +Pass role: banner +Pass role: blockquote +Pass role: button +Pass role: caption +Pass role: checkbox +Pass role: code +Pass role: combobox +Pass role: complementary +Pass role: contentinfo +Pass role: definition +Pass role: deletion +Pass role: dialog +Pass role: document +Pass role: emphasis +Pass role: feed +Pass role: figure +Pass role: generic +Pass role: group +Pass role: heading +Pass role: insertion +Pass role: link +Pass role: log +Pass role: main +Pass role: marquee +Pass role: math +Pass role: meter +Pass role: navigation +Pass role: note +Pass role: paragraph +Pass role: progressbar +Pass role: radio +Pass role: radiogroup +Pass role: scrollbar +Pass role: search +Pass role: searchbox +Pass role: separator +Pass role: slider +Pass role: spinbutton +Pass role: status +Pass role: strong +Pass role: subscript +Fail role: suggestion +Pass role: superscript +Pass role: switch +Pass role: term +Pass role: textbox +Pass role: time +Pass role: timer +Pass role: toolbar +Pass role: tooltip \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/roles.html b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/roles.html new file mode 100644 index 0000000000000..a8ddb2e903984 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/roles.html @@ -0,0 +1,140 @@ + + + + Simple Core ARIA Role Verification Tests + + + + + + + + + +

Tests most ARIA role definitions. See comments for more info.

+ + + + \ No newline at end of file From dd283768a86bee167192c635b5b59c3e01a3afc6 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Tue, 10 Dec 2024 00:27:18 -0500 Subject: [PATCH 068/237] LibWeb/CSS: Allow `none` values in the `color()` function --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index b377dccb0e885..55f0988eb7d40 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3458,17 +3458,17 @@ RefPtr Parser::parse_color_function(TokenStream& auto const& color_space = maybe_color_space.token().ident(); - auto c1 = parse_number_percentage_value(inner_tokens); + auto c1 = parse_number_percentage_none_value(inner_tokens); if (!c1) return {}; inner_tokens.discard_whitespace(); - auto c2 = parse_number_percentage_value(inner_tokens); + auto c2 = parse_number_percentage_none_value(inner_tokens); if (!c2) return {}; inner_tokens.discard_whitespace(); - auto c3 = parse_number_percentage_value(inner_tokens); + auto c3 = parse_number_percentage_none_value(inner_tokens); if (!c3) return {}; inner_tokens.discard_whitespace(); From d5cdda0b40cf17b0f84ddc048ca246a9d2e965c4 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Wed, 4 Dec 2024 01:59:59 +0400 Subject: [PATCH 069/237] LibWeb: Load external scripts in SVG --- Libraries/LibWeb/SVG/SVGScriptElement.cpp | 85 ++++++++++++++++--- .../syntax/parsing/unclosed-svg-script.txt | 6 +- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/Libraries/LibWeb/SVG/SVGScriptElement.cpp b/Libraries/LibWeb/SVG/SVGScriptElement.cpp index 4bf97b9d40bfe..8b869140654dc 100644 --- a/Libraries/LibWeb/SVG/SVGScriptElement.cpp +++ b/Libraries/LibWeb/SVG/SVGScriptElement.cpp @@ -1,10 +1,13 @@ /* * Copyright (c) 2023-2024, Shannon Booth + * Copyright (c) 2024, Pavel Shliak * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include +#include #include #include #include @@ -40,15 +43,76 @@ void SVGScriptElement::process_the_script_element() if (m_already_processed || !in_a_document_tree()) return; - auto inline_script = child_text_content(); + IGNORE_USE_IN_ESCAPING_LAMBDA String script_content; + auto script_url = m_document->url(); - // FIXME: 2. If the 'script' element references external script content, then the external script content - // using the current value of the 'xlink:href' attribute is fetched. Further processing of the - // 'script' element is dependent on the external script content, and will block here until the - // resource has been fetched or is determined to be an invalid IRI reference. + // 2. If the 'script' element references external script content, then the external script content + // using the current value of the 'xlink:href' attribute is fetched. Further processing of the + // 'script' element is dependent on the external script content, and will block here until the + // resource has been fetched or is determined to be an invalid IRI reference. if (has_attribute(SVG::AttributeNames::href) || has_attribute_ns(Namespace::XLink.to_string(), SVG::AttributeNames::href)) { - dbgln("FIXME: Unsupported external fetch of SVGScriptElement!"); - return; + auto href_value = href()->base_val(); + + script_url = document().parse_url(href_value); + if (!script_url.is_valid()) { + dbgln("Invalid script URL: {}", href_value); + return; + } + + auto& vm = realm().vm(); + auto request = Fetch::Infrastructure::Request::create(vm); + request->set_url(script_url); + request->set_destination(Fetch::Infrastructure::Request::Destination::Script); + // FIXME: Use CORS state specified by the ‘crossorigin’ attribute. + request->set_mode(Fetch::Infrastructure::Request::Mode::NoCORS); + request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::SameOrigin); + request->set_client(&document().relevant_settings_object()); + + IGNORE_USE_IN_ESCAPING_LAMBDA bool fetch_done = false; + + Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; + fetch_algorithms_input.process_response = [this, &script_content, &fetch_done](GC::Ref response) { + if (response->is_network_error()) { + dbgln("Failed to fetch SVG external script."); + fetch_done = true; + return; + } + + auto& realm = this->realm(); + auto& global = document().realm().global_object(); + + auto on_data_read = GC::create_function(realm.heap(), [&script_content, &fetch_done](ByteBuffer data) { + auto content_or_error = String::from_utf8(data); + if (content_or_error.is_error()) { + dbgln("Failed to decode script content as UTF-8"); + } else { + script_content = content_or_error.release_value(); + } + fetch_done = true; + }); + + auto on_error = GC::create_function(realm.heap(), [&fetch_done](JS::Value) { + dbgln("Error occurred while reading script data."); + fetch_done = true; + }); + + VERIFY(response->body()); + response->body()->fully_read(realm, on_data_read, on_error, GC::Ref { global }); + }; + + auto fetch_promise = Fetch::Fetching::fetch(realm(), request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))); + + // Block until the resource has been fetched or determined invalid + HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&] { return fetch_done; })); + + if (script_content.is_empty()) { + // Failed to fetch or decode + return; + } + + } else { + // Inline script content + script_content = child_text_content(); } // 3. The 'script' element's "already processed" flag is set to true. @@ -63,10 +127,11 @@ void SVGScriptElement::process_the_script_element() if (!m_document->ready_to_run_scripts()) HTML::main_thread_event_loop().spin_until(GC::create_function(heap(), [&] { return m_document->ready_to_run_scripts(); })); - // FIXME: Support non-inline scripts. - auto base_url = document().base_url(); + m_script = HTML::ClassicScript::create(script_url.basename(), script_content, realm(), m_document->base_url(), m_source_line_number); + + // FIXME: Note that a load event is dispatched on a 'script' element once it has been processed, + // unless it referenced external script content with an invalid IRI reference and 'externalResourcesRequired' was set to 'true'. - m_script = HTML::ClassicScript::create(m_document->url().to_byte_string(), inline_script, realm(), base_url, m_source_line_number); (void)m_script->run(); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/syntax/parsing/unclosed-svg-script.txt b/Tests/LibWeb/Text/expected/wpt-import/html/syntax/parsing/unclosed-svg-script.txt index 24100ea6c5d66..3c683d8b4e393 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/syntax/parsing/unclosed-svg-script.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/syntax/parsing/unclosed-svg-script.txt @@ -2,10 +2,10 @@ Harness status: OK Found 5 tests -3 Pass -2 Fail +4 Pass +1 Fail Pass SVG scripts with end tag should run Fail SVG scripts without end tag should not run Pass SVG scripts with bogus end tag inside should run Pass SVG scripts ended by HTML breakout should not run -Fail SVG scripts with self-closing start tag should run \ No newline at end of file +Pass SVG scripts with self-closing start tag should run \ No newline at end of file From a4f1838798933675630c47ce039537d4ad853e54 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 11:05:57 +0000 Subject: [PATCH 070/237] Meta: Add a `list-tests` command to WPT.sh This outputs a list of all the tests that would be run in the given test directories. --- Meta/WPT.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Meta/WPT.sh b/Meta/WPT.sh index 64777e5677555..061fc2a974016 100755 --- a/Meta/WPT.sh +++ b/Meta/WPT.sh @@ -60,6 +60,8 @@ print_help() { Run the Web Platform Tests comparing the results to the expectations in LOG_FILE. import: $NAME import [TESTS...] Fetch the given test file(s) from https://wpt.live/ and create an in-tree test and expectation files. + list-tests: $NAME list-tests [PATHS..] + List the tests in the given PATHS. Examples: $NAME update @@ -76,6 +78,8 @@ print_help() { Run the Web Platform Tests in the 'css/CSS2' directory, comparing the results to the expectations in expectations.log; output the results to results.log. $NAME import html/dom/aria-attribute-reflection.html Import the test from https://wpt.live/html/dom/aria-attribute-reflection.html into the Ladybird test suite. + $NAME list-tests css/CSS2 dom + Show a list of all tests in the 'css/CSS2' and 'dom' directories. EOF } @@ -199,6 +203,15 @@ serve_wpt() popd > /dev/null } +list_tests_wpt() +{ + ensure_wpt_repository + + pushd "${WPT_SOURCE_DIR}" > /dev/null + ./wpt run --list-tests ladybird "${TEST_LIST[@]}" + popd > /dev/null +} + import_wpt() { for i in "${!INPUT_PATHS[@]}"; do @@ -229,7 +242,7 @@ compare_wpt() { rm -rf "${METADATA_DIR}" } -if [[ "$CMD" =~ ^(update|run|serve|compare|import)$ ]]; then +if [[ "$CMD" =~ ^(update|run|serve|compare|import|list-tests)$ ]]; then case "$CMD" in update) update_wpt @@ -257,6 +270,9 @@ if [[ "$CMD" =~ ^(update|run|serve|compare|import)$ ]]; then shift compare_wpt ;; + list-tests) + list_tests_wpt + ;; esac else >&2 echo "Unknown command: $CMD" From cb8a1f6bb626a07face4eca2dfa5438cab512fdf Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 11:37:43 +0000 Subject: [PATCH 071/237] Meta: Add the ability to import entire test directories with `WPT.sh` This uses the `WPT.sh list-tests` command to find which tests are in each given path. --- Meta/WPT.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Meta/WPT.sh b/Meta/WPT.sh index 061fc2a974016..263e043a8f82b 100755 --- a/Meta/WPT.sh +++ b/Meta/WPT.sh @@ -58,7 +58,7 @@ print_help() { Run the Web Platform Tests. compare: $NAME compare [OPTIONS...] LOG_FILE [TESTS...] Run the Web Platform Tests comparing the results to the expectations in LOG_FILE. - import: $NAME import [TESTS...] + import: $NAME import [PATHS...] Fetch the given test file(s) from https://wpt.live/ and create an in-tree test and expectation files. list-tests: $NAME list-tests [PATHS..] List the tests in the given PATHS. @@ -220,9 +220,21 @@ import_wpt() item="${item#https://wpt.live/}" INPUT_PATHS[i]="$item" done + + TESTS=() + while IFS= read -r test_file; do + TESTS+=("$test_file") + done < <( + "${ARG0}" list-tests "${INPUT_PATHS[@]}" + ) + if [ "${#TESTS[@]}" -eq 0 ]; then + echo "No tests found for the given paths" + exit 1 + fi + pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null ./Meta/ladybird.sh build headless-browser - for path in "${INPUT_PATHS[@]}"; do + for path in "${TESTS[@]}"; do echo "Importing test from ${path}" ./Meta/import-wpt-test.py https://wpt.live/"${path}" "${HEADLESS_BROWSER_BINARY}" --run-tests ./Tests/LibWeb --rebaseline -f "$path" From 93a1c45eb213b2aed08922c5d79a9a9014e39434 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Mon, 9 Dec 2024 11:39:05 +0000 Subject: [PATCH 072/237] Meta: Don't halt if importing a test fails in `WPT.sh` --- Meta/WPT.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Meta/WPT.sh b/Meta/WPT.sh index 263e043a8f82b..1f2eb1e9f0607 100755 --- a/Meta/WPT.sh +++ b/Meta/WPT.sh @@ -234,11 +234,15 @@ import_wpt() pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null ./Meta/ladybird.sh build headless-browser + set +e for path in "${TESTS[@]}"; do echo "Importing test from ${path}" - ./Meta/import-wpt-test.py https://wpt.live/"${path}" + if [ ! "$(./Meta/import-wpt-test.py https://wpt.live/"${path}")" ]; then + continue + fi "${HEADLESS_BROWSER_BINARY}" --run-tests ./Tests/LibWeb --rebaseline -f "$path" done + set -e popd > /dev/null } From 9c243caac4c3c77921af537cddd317d1f6c2c6da Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Tue, 10 Dec 2024 10:53:02 -0800 Subject: [PATCH 073/237] LibWeb: Add statusText validation for Response constructor Implemented validation to ensure `statusText` matches the `reason-phrase` token production. This change fixes a WPT subtest which I have imported. --- Libraries/LibWeb/Fetch/Response.cpp | 16 ++++++++++- .../fetch/api/response/response-error.any.txt | 16 +++++++++++ .../api/response/response-error.any.html | 15 +++++++++++ .../fetch/api/response/response-error.any.js | 27 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-error.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.js diff --git a/Libraries/LibWeb/Fetch/Response.cpp b/Libraries/LibWeb/Fetch/Response.cpp index 3bb89a8f2e4c5..6d3c133621267 100644 --- a/Libraries/LibWeb/Fetch/Response.cpp +++ b/Libraries/LibWeb/Fetch/Response.cpp @@ -84,6 +84,18 @@ GC::Ref Response::create(JS::Realm& realm, GC::Ref= 0x21 && c <= 0x7E) || (c >= 0x80 && c <= 0xFF); + }); +} + // https://fetch.spec.whatwg.org/#initialize-a-response WebIDL::ExceptionOr Response::initialize_response(ResponseInit const& init, Optional const& body) { @@ -91,7 +103,9 @@ WebIDL::ExceptionOr Response::initialize_response(ResponseInit const& init if (init.status < 200 || init.status > 599) return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be in range 200-599"sv }; - // FIXME: 2. If init["statusText"] does not match the reason-phrase token production, then throw a TypeError. + // 2. If init["statusText"] does not match the reason-phrase token production, then throw a TypeError. + if (!is_valid_status_text(init.status_text)) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid statusText: does not match the reason-phrase token production"sv }; // 3. Set response’s response’s status to init["status"]. m_response->set_status(init.status); diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-error.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-error.any.txt new file mode 100644 index 0000000000000..7b365758017dc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-error.any.txt @@ -0,0 +1,16 @@ +Harness status: OK + +Found 10 tests + +10 Pass +Pass Throws RangeError when responseInit's status is 0 +Pass Throws RangeError when responseInit's status is 100 +Pass Throws RangeError when responseInit's status is 199 +Pass Throws RangeError when responseInit's status is 600 +Pass Throws RangeError when responseInit's status is 1000 +Pass Throws TypeError when responseInit's statusText is + +Pass Throws TypeError when responseInit's statusText is Ā +Pass Throws TypeError when building a response with body and a body status of 204 +Pass Throws TypeError when building a response with body and a body status of 205 +Pass Throws TypeError when building a response with body and a body status of 304 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.html new file mode 100644 index 0000000000000..f7c4a6e534409 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.html @@ -0,0 +1,15 @@ + + +Response error + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.js new file mode 100644 index 0000000000000..a76bc4380286f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-error.any.js @@ -0,0 +1,27 @@ +// META: global=window,worker +// META: title=Response error + +var invalidStatus = [0, 100, 199, 600, 1000]; +invalidStatus.forEach(function(status) { + test(function() { + assert_throws_js(RangeError, function() { new Response("", { "status" : status }); }, + "Expect RangeError exception when status is " + status); + },"Throws RangeError when responseInit's status is " + status); +}); + +var invalidStatusText = ["\n", "Ā"]; +invalidStatusText.forEach(function(statusText) { + test(function() { + assert_throws_js(TypeError, function() { new Response("", { "statusText" : statusText }); }, + "Expect TypeError exception " + statusText); + },"Throws TypeError when responseInit's statusText is " + statusText); +}); + +var nullBodyStatus = [204, 205, 304]; +nullBodyStatus.forEach(function(status) { + test(function() { + assert_throws_js(TypeError, + function() { new Response("body", {"status" : status }); }, + "Expect TypeError exception "); + },"Throws TypeError when building a response with body and a body status of " + status); +}); From 5e62f548db109366972d7d93b45afee784356401 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Wed, 27 Nov 2024 00:45:02 -0500 Subject: [PATCH 074/237] LibWeb/CSS: Serialize CSSColor without relying on RGB This gives us 140 subtests pass in: css/css-color/parsing/color-valid-color-function.html --- Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp | 54 ++- Libraries/LibWeb/CSS/StyleValues/CSSColor.h | 11 +- .../LibWeb/CSS/StyleValues/CSSColorValue.h | 1 - .../parsing/color-valid-color-function.txt | 312 ++++++++++++++++++ .../parsing/color-valid-color-function.html | 59 ++++ 5 files changed, 430 insertions(+), 7 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/css-color/parsing/color-valid-color-function.html diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp b/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp index be38c9903169b..873ba1583ffe5 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp @@ -34,6 +34,27 @@ CSSColorValue::ColorType color_type_from_string_view(StringView color_space) VERIFY_NOT_REACHED(); } +StringView string_view_from_color_type(CSSColorValue::ColorType color_type) +{ + if (color_type == CSSColorValue::ColorType::A98RGB) + return "a98-rgb"sv; + if (color_type == CSSColorValue::ColorType::DisplayP3) + return "display-p3"sv; + if (color_type == CSSColorValue::ColorType::sRGB) + return "srgb"sv; + if (color_type == CSSColorValue::ColorType::sRGBLinear) + return "srgb-linear"sv; + if (color_type == CSSColorValue::ColorType::ProPhotoRGB) + return "prophoto-rgb"sv; + if (color_type == CSSColorValue::ColorType::Rec2020) + return "rec2020"sv; + if (color_type == CSSColorValue::ColorType::XYZD50) + return "xyz-d50"sv; + if (color_type == CSSColorValue::ColorType::XYZD65) + return "xyz"sv; + VERIFY_NOT_REACHED(); +} + } ValueComparingNonnullRefPtr CSSColor::create(StringView color_space, ValueComparingNonnullRefPtr c1, ValueComparingNonnullRefPtr c2, ValueComparingNonnullRefPtr c3, ValueComparingRefPtr alpha) @@ -59,19 +80,42 @@ bool CSSColor::equals(CSSStyleValue const& other) const return m_properties == other_lab_like.m_properties; } +CSSColor::Resolved CSSColor::resolve_properties() const +{ + float const c1 = resolve_with_reference_value(m_properties.channels[0], 1).value_or(0); + float const c2 = resolve_with_reference_value(m_properties.channels[1], 1).value_or(0); + float const c3 = resolve_with_reference_value(m_properties.channels[2], 1).value_or(0); + float const alpha_val = resolve_alpha(m_properties.alpha).value_or(1); + return { .channels = { c1, c2, c3 }, .alpha = alpha_val }; +} + // https://www.w3.org/TR/css-color-4/#serializing-color-function-values String CSSColor::to_string(SerializationMode) const { // FIXME: Do this properly, taking unresolved calculated values into account. - return serialize_a_srgb_value(to_color({})); + auto resolved = resolve_properties(); + if (resolved.alpha == 1) { + return MUST(String::formatted("color({} {} {} {})", + string_view_from_color_type(m_color_type), + resolved.channels[0], + resolved.channels[1], + resolved.channels[2])); + } + + return MUST(String::formatted("color({} {} {} {} / {})", + string_view_from_color_type(m_color_type), + resolved.channels[0], + resolved.channels[1], + resolved.channels[2], + resolved.alpha)); } Color CSSColor::to_color(Optional) const { - auto const c1 = resolve_with_reference_value(m_properties.channels[0], 1).value_or(0); - auto const c2 = resolve_with_reference_value(m_properties.channels[1], 1).value_or(0); - auto const c3 = resolve_with_reference_value(m_properties.channels[2], 1).value_or(0); - auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1); + auto [channels, alpha_val] = resolve_properties(); + auto c1 = channels[0]; + auto c2 = channels[1]; + auto c3 = channels[2]; if (color_type() == ColorType::A98RGB) return Color::from_a98rgb(c1, c2, c3, alpha_val); diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColor.h b/Libraries/LibWeb/CSS/StyleValues/CSSColor.h index b46108abe3f0c..671f632b57c33 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColor.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColor.h @@ -34,7 +34,16 @@ class CSSColor final : public CSSColorValue { Array, 3> channels; ValueComparingNonnullRefPtr alpha; bool operator==(Properties const&) const = default; - } m_properties; + }; + + struct Resolved { + Array channels {}; + float alpha {}; + }; + + Resolved resolve_properties() const; + + Properties m_properties; }; } // Web::CSS diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h index c73d006e24e8d..225a4e8fb4d6c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h @@ -53,7 +53,6 @@ class CSSColorValue : public CSSStyleValue { static Optional resolve_with_reference_value(CSSStyleValue const&, float one_hundred_percent_value); static Optional resolve_alpha(CSSStyleValue const&); -private: ColorType m_color_type; }; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt new file mode 100644 index 0000000000000..67026febdfd33 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt @@ -0,0 +1,312 @@ +Harness status: OK + +Found 306 tests + +140 Pass +166 Fail +Pass e.style['color'] = "color(srgb 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(srgb 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(srgb .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(srgb 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(srgb 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(srgb 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(srgb 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(srgb 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(srgb 50% -160 160)" should set the property value +Pass e.style['color'] = "color(srgb 50% -200 200)" should set the property value +Pass e.style['color'] = "color(srgb 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(srgb 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(srgb 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(srgb 200 200 200)" should set the property value +Pass e.style['color'] = "color(srgb 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(srgb -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(srgb -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(srgb 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(srgb 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(srgb -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(srgb calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(srgb calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(srgb calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(srgb none none none / none)" should set the property value +Fail e.style['color'] = "color(srgb none none none)" should set the property value +Fail e.style['color'] = "color(srgb 10% none none / none)" should set the property value +Fail e.style['color'] = "color(srgb none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(srgb 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(srgb 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(srgb 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(srgb calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(srgb calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(srgb calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(srgb 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(srgb-linear 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(srgb-linear .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(srgb-linear 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(srgb-linear 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(srgb-linear 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 50% -160 160)" should set the property value +Pass e.style['color'] = "color(srgb-linear 50% -200 200)" should set the property value +Pass e.style['color'] = "color(srgb-linear 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 200 200 200)" should set the property value +Pass e.style['color'] = "color(srgb-linear 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(srgb-linear -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(srgb-linear -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(srgb-linear 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(srgb-linear 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(srgb-linear -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(srgb-linear none none none / none)" should set the property value +Fail e.style['color'] = "color(srgb-linear none none none)" should set the property value +Fail e.style['color'] = "color(srgb-linear 10% none none / none)" should set the property value +Fail e.style['color'] = "color(srgb-linear none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(srgb-linear 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(srgb-linear 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(srgb-linear 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(srgb-linear calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(srgb-linear 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(a98-rgb 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(a98-rgb .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(a98-rgb 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(a98-rgb 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(a98-rgb 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 50% -160 160)" should set the property value +Pass e.style['color'] = "color(a98-rgb 50% -200 200)" should set the property value +Pass e.style['color'] = "color(a98-rgb 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 200 200 200)" should set the property value +Pass e.style['color'] = "color(a98-rgb 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(a98-rgb -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(a98-rgb -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(a98-rgb 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(a98-rgb 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(a98-rgb -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(a98-rgb none none none / none)" should set the property value +Fail e.style['color'] = "color(a98-rgb none none none)" should set the property value +Fail e.style['color'] = "color(a98-rgb 10% none none / none)" should set the property value +Fail e.style['color'] = "color(a98-rgb none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(a98-rgb 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(a98-rgb 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(a98-rgb 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(a98-rgb calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(a98-rgb 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(rec2020 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(rec2020 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(rec2020 .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(rec2020 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(rec2020 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(rec2020 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(rec2020 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(rec2020 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(rec2020 50% -160 160)" should set the property value +Pass e.style['color'] = "color(rec2020 50% -200 200)" should set the property value +Pass e.style['color'] = "color(rec2020 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(rec2020 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(rec2020 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(rec2020 200 200 200)" should set the property value +Pass e.style['color'] = "color(rec2020 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(rec2020 -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(rec2020 -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(rec2020 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(rec2020 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(rec2020 -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(rec2020 calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(rec2020 calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(rec2020 calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(rec2020 none none none / none)" should set the property value +Fail e.style['color'] = "color(rec2020 none none none)" should set the property value +Fail e.style['color'] = "color(rec2020 10% none none / none)" should set the property value +Fail e.style['color'] = "color(rec2020 none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(rec2020 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(rec2020 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(rec2020 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(rec2020 calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(rec2020 calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(rec2020 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(rec2020 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 50% -160 160)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 50% -200 200)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 200 200 200)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(prophoto-rgb -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb none none none / none)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb none none none)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb 10% none none / none)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(prophoto-rgb 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(display-p3 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(display-p3 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(display-p3 .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(display-p3 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(display-p3 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(display-p3 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(display-p3 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(display-p3 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(display-p3 50% -160 160)" should set the property value +Pass e.style['color'] = "color(display-p3 50% -200 200)" should set the property value +Pass e.style['color'] = "color(display-p3 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(display-p3 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(display-p3 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(display-p3 200 200 200)" should set the property value +Pass e.style['color'] = "color(display-p3 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(display-p3 -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(display-p3 -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(display-p3 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(display-p3 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(display-p3 -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(display-p3 calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(display-p3 calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(display-p3 calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(display-p3 none none none / none)" should set the property value +Fail e.style['color'] = "color(display-p3 none none none)" should set the property value +Fail e.style['color'] = "color(display-p3 10% none none / none)" should set the property value +Fail e.style['color'] = "color(display-p3 none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(display-p3 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(display-p3 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(display-p3 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(display-p3 calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(display-p3 calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(display-p3 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(display-p3 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Fail e.style['color'] = "color(xyz 0% 0% 0%)" should set the property value +Fail e.style['color'] = "color(xyz 10% 10% 10%)" should set the property value +Fail e.style['color'] = "color(xyz .2 .2 25%)" should set the property value +Fail e.style['color'] = "color(xyz 0 0 0 / 1)" should set the property value +Fail e.style['color'] = "color(xyz 0% 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz 20% 0 10/0.5)" should set the property value +Fail e.style['color'] = "color(xyz 20% 0 10/50%)" should set the property value +Fail e.style['color'] = "color(xyz 400% 0 10/50%)" should set the property value +Fail e.style['color'] = "color(xyz 50% -160 160)" should set the property value +Fail e.style['color'] = "color(xyz 50% -200 200)" should set the property value +Fail e.style['color'] = "color(xyz 0 0 0 / -10%)" should set the property value +Fail e.style['color'] = "color(xyz 0 0 0 / 110%)" should set the property value +Fail e.style['color'] = "color(xyz 0 0 0 / 300%)" should set the property value +Fail e.style['color'] = "color(xyz 200 200 200)" should set the property value +Fail e.style['color'] = "color(xyz 200 200 200 / 200)" should set the property value +Fail e.style['color'] = "color(xyz -200 -200 -200)" should set the property value +Fail e.style['color'] = "color(xyz -200 -200 -200 / -200)" should set the property value +Fail e.style['color'] = "color(xyz 200% 200% 200%)" should set the property value +Fail e.style['color'] = "color(xyz 200% 200% 200% / 200%)" should set the property value +Fail e.style['color'] = "color(xyz -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(xyz calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(xyz calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(xyz calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(xyz none none none / none)" should set the property value +Fail e.style['color'] = "color(xyz none none none)" should set the property value +Fail e.style['color'] = "color(xyz 10% none none / none)" should set the property value +Fail e.style['color'] = "color(xyz none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(xyz 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Pass e.style['color'] = "color(xyz-d50 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(xyz-d50 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(xyz-d50 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(xyz-d50 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 50% -160 160)" should set the property value +Pass e.style['color'] = "color(xyz-d50 50% -200 200)" should set the property value +Pass e.style['color'] = "color(xyz-d50 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 200 200 200)" should set the property value +Pass e.style['color'] = "color(xyz-d50 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(xyz-d50 -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(xyz-d50 -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(xyz-d50 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(xyz-d50 -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d50 none none none / none)" should set the property value +Fail e.style['color'] = "color(xyz-d50 none none none)" should set the property value +Fail e.style['color'] = "color(xyz-d50 10% none none / none)" should set the property value +Fail e.style['color'] = "color(xyz-d50 none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d50 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(xyz-d50 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz-d50 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz-d50 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d50 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value +Fail e.style['color'] = "color(xyz-d65 0% 0% 0%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 10% 10% 10%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 .2 .2 25%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 0 0 / 1)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0% 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d65 20% 0 10/0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d65 20% 0 10/50%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 400% 0 10/50%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 50% -160 160)" should set the property value +Fail e.style['color'] = "color(xyz-d65 50% -200 200)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 0 0 / -10%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 0 0 / 110%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 0 0 / 300%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 200 200 200)" should set the property value +Fail e.style['color'] = "color(xyz-d65 200 200 200 / 200)" should set the property value +Fail e.style['color'] = "color(xyz-d65 -200 -200 -200)" should set the property value +Fail e.style['color'] = "color(xyz-d65 -200 -200 -200 / -200)" should set the property value +Fail e.style['color'] = "color(xyz-d65 200% 200% 200%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 200% 200% 200% / 200%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 -200% -200% -200% / -200%)" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(50%) 50% 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d65 none none none / none)" should set the property value +Fail e.style['color'] = "color(xyz-d65 none none none)" should set the property value +Fail e.style['color'] = "color(xyz-d65 10% none none / none)" should set the property value +Fail e.style['color'] = "color(xyz-d65 none none none / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 0 0 / none)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 calc(infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0 calc(-infinity) 0)" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(NaN) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(0 / 0) 0 0)" should set the property value +Fail e.style['color'] = "color(xyz-d65 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value +Fail e.style['color'] = "color(xyz-d65 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-color/parsing/color-valid-color-function.html b/Tests/LibWeb/Text/input/wpt-import/css/css-color/parsing/color-valid-color-function.html new file mode 100644 index 0000000000000..592066a78add6 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-color/parsing/color-valid-color-function.html @@ -0,0 +1,59 @@ + + + + +CSS Color Level 4: Parsing and serialization of colors using valid color() function syntax + + + + + + + + + + + From 824e91ffdb568e28ec1640d28cf4f45d2fe4a0ae Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Fri, 6 Dec 2024 09:35:02 -0800 Subject: [PATCH 075/237] LibWeb: Ensure Headers API can handle non-ascii characters This patch ensure Headers object's associated header list is ISO-8859-1 encoded when set using `Infra::isomorphic_encode`, and correctly decoded using `Infra::isomorphic_decode`. Follow-up of https://github.com/LadybirdBrowser/ladybird/pull/1893 --- Libraries/LibWeb/Fetch/Headers.cpp | 17 ++--- Libraries/LibWeb/Fetch/HeadersIterator.cpp | 5 +- .../Fetch/fetch-headers-non-ascii.txt | 34 ++++++++++ .../api/headers/headers-normalize.any.txt | 8 +++ .../input/Fetch/fetch-headers-non-ascii.html | 66 +++++++++++++++++++ .../api/headers/headers-normalize.any.html | 15 +++++ .../api/headers/headers-normalize.any.js | 56 ++++++++++++++++ 7 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt create mode 100644 Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js diff --git a/Libraries/LibWeb/Fetch/Headers.cpp b/Libraries/LibWeb/Fetch/Headers.cpp index 1d4dbf31a7ecf..b60503d7c270b 100644 --- a/Libraries/LibWeb/Fetch/Headers.cpp +++ b/Libraries/LibWeb/Fetch/Headers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Fetch { @@ -56,10 +57,7 @@ void Headers::visit_edges(JS::Cell::Visitor& visitor) WebIDL::ExceptionOr Headers::append(String const& name_string, String const& value_string) { // The append(name, value) method steps are to append (name, value) to this. - auto header = Infrastructure::Header { - .name = MUST(ByteBuffer::copy(name_string.bytes())), - .value = MUST(ByteBuffer::copy(value_string.bytes())), - }; + auto header = Infrastructure::Header::from_string_pair(name_string, value_string); TRY(append(move(header))); return {}; } @@ -106,7 +104,7 @@ WebIDL::ExceptionOr> Headers::get(String const& name_string) // 2. Return the result of getting name from this’s header list. auto byte_buffer = m_header_list->get(name); - return byte_buffer.has_value() ? MUST(String::from_utf8(*byte_buffer)) : Optional {}; + return byte_buffer.has_value() ? Infra::isomorphic_decode(*byte_buffer) : Optional {}; } // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie @@ -123,7 +121,7 @@ Vector Headers::get_set_cookie() // `Set-Cookie`, in order. for (auto const& header : *m_header_list) { if (StringView { header.name }.equals_ignoring_ascii_case("Set-Cookie"sv)) - values.append(MUST(String::from_utf8(header.value))); + values.append(Infra::isomorphic_decode(header.value)); } return values; } @@ -152,10 +150,7 @@ WebIDL::ExceptionOr Headers::set(String const& name_string, String const& // 1. Normalize value. auto normalized_value = Infrastructure::normalize_header_value(value); - auto header = Infrastructure::Header { - .name = MUST(ByteBuffer::copy(name)), - .value = move(normalized_value), - }; + auto header = Infrastructure::Header::from_string_pair(name, normalized_value); // 2. If validating (name, value) for headers returns false, then return. if (!TRY(validate(header))) @@ -197,7 +192,7 @@ JS::ThrowCompletionOr Headers::for_each(ForEachCallback callback) auto const& pair = pairs[i]; // 2. Invoke idlCallback with « pair’s value, pair’s key, idlObject » and with thisArg as the callback this value. - TRY(callback(MUST(String::from_utf8(pair.name)), MUST(String::from_utf8(pair.value)))); + TRY(callback(Infra::isomorphic_decode(pair.name), Infra::isomorphic_decode(pair.value))); // 3. Set pairs to idlObject’s current list of value pairs to iterate over. (It might have changed.) pairs = value_pairs_to_iterate_over(); diff --git a/Libraries/LibWeb/Fetch/HeadersIterator.cpp b/Libraries/LibWeb/Fetch/HeadersIterator.cpp index 3b25fa055b943..8279fa27cdd04 100644 --- a/Libraries/LibWeb/Fetch/HeadersIterator.cpp +++ b/Libraries/LibWeb/Fetch/HeadersIterator.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Bindings { @@ -65,8 +66,8 @@ GC::Ref HeadersIterator::next() return create_iterator_result_object(vm(), JS::js_undefined(), true); auto const& pair = pairs[m_index++]; - StringView pair_name { pair.name }; - StringView pair_value { pair.value }; + auto pair_name = Infra::isomorphic_decode(pair.name); + auto pair_value = Infra::isomorphic_decode(pair.value); switch (m_iteration_kind) { case JS::Object::PropertyKind::Key: diff --git a/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt b/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt new file mode 100644 index 0000000000000..36d42bde131d5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt @@ -0,0 +1,34 @@ +-------------------------------- +Headers constructor +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.append() +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.set() +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.getSetCookie() +-------------------------------- +Set-Cookie: before-æøå-after + +-------------------------------- +Headers iterator +-------------------------------- +accept: before-æøå-after +x-test: before-ß-after + +-------------------------------- +Headers.forEach() +-------------------------------- +accept: before-æøå-after +x-test: before-ß-after diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt new file mode 100644 index 0000000000000..6da8698508e74 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass Create headers with not normalized values +Pass Check append method with not normalized values +Pass Check set method with not normalized values \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html b/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html new file mode 100644 index 0000000000000..d2f066fcd820c --- /dev/null +++ b/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html @@ -0,0 +1,66 @@ + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html new file mode 100644 index 0000000000000..ac89e6d834474 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html @@ -0,0 +1,15 @@ + + +Headers normalize values + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js new file mode 100644 index 0000000000000..68cf5b85f3acb --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js @@ -0,0 +1,56 @@ +// META: title=Headers normalize values +// META: global=window,worker + +"use strict"; + +const expectations = { + "name1": [" space ", "space"], + "name2": ["\ttab\t", "tab"], + "name3": [" spaceAndTab\t", "spaceAndTab"], + "name4": ["\r\n newLine", "newLine"], //obs-fold cases + "name5": ["newLine\r\n ", "newLine"], + "name6": ["\r\n\tnewLine", "newLine"], + "name7": ["\t\f\tnewLine\n", "\f\tnewLine"], + "name8": ["newLine\xa0", "newLine\xa0"], // \xa0 == non breaking space +}; + +test(function () { + const headerDict = Object.fromEntries( + Object.entries(expectations).map(([name, [actual]]) => [name, actual]), + ); + var headers = new Headers(headerDict); + for (const name in expectations) { + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has normalized value: " + expected, + ); + } +}, "Create headers with not normalized values"); + +test(function () { + var headers = new Headers(); + for (const name in expectations) { + headers.append(name, expectations[name][0]); + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has value: " + expected, + ); + } +}, "Check append method with not normalized values"); + +test(function () { + var headers = new Headers(); + for (const name in expectations) { + headers.set(name, expectations[name][0]); + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has value: " + expected, + ); + } +}, "Check set method with not normalized values"); From 2d638485a89dba1cbd4928512fc43681c92c3581 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Mon, 9 Dec 2024 17:01:54 +0900 Subject: [PATCH 076/237] =?UTF-8?q?LibWeb:=20Assign=20=E2=80=9Corphaned?= =?UTF-8?q?=E2=80=9D=20li=20elements=20the=20default=20ARIA=20role=20?= =?UTF-8?q?=E2=80=9Cnone=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes Ladybird conform to the current requirements at https://w3c.github.io/core-aam/#roleMappingComputedRole in the “Core Accessibility API Mappings” spec for the case of “orphaned” li elements; that is, any li element which doesn’t have a role=list ancestor. The core-aam spec requires that in such cases, the li element must not be assigned the “listitem” role but instead must be treated as if it had no role at all. --- Libraries/LibWeb/HTML/HTMLLIElement.h | 17 +++++++++++++-- .../html-aam/roles-generic.tentative.txt | 6 ++++++ .../html-aam/roles-generic.tentative.html | 21 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.tentative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.tentative.html diff --git a/Libraries/LibWeb/HTML/HTMLLIElement.h b/Libraries/LibWeb/HTML/HTMLLIElement.h index d03e391104fe8..362b162dcdc94 100644 --- a/Libraries/LibWeb/HTML/HTMLLIElement.h +++ b/Libraries/LibWeb/HTML/HTMLLIElement.h @@ -6,8 +6,10 @@ #pragma once -#include +#include #include +#include +#include #include namespace Web::HTML { @@ -20,7 +22,18 @@ class HTMLLIElement final : public HTMLElement { virtual ~HTMLLIElement() override; // https://www.w3.org/TR/html-aria/#el-li - virtual Optional default_role() const override { return ARIA::Role::listitem; } + virtual Optional default_role() const override + { + for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { + if (ancestor->role_or_default() == ARIA::Role::list) + return ARIA::Role::listitem; + } + // https://w3c.github.io/core-aam/#roleMappingComputedRole + // When an element has a role but is not contained in the required context (for example, an orphaned listitem + // without the required accessible parent of role list), User Agents MUST ignore the role token, and return the + // computedrole as if the ignored role token had not been included. + return ARIA::Role::none; + } WebIDL::Long value(); void set_value(WebIDL::Long value) diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.tentative.txt new file mode 100644 index 0000000000000..43ee599a269b0 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.tentative.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass el-li-orphaned \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.tentative.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.tentative.html new file mode 100644 index 0000000000000..150ea5a9ae1bb --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.tentative.html @@ -0,0 +1,21 @@ + + + + Tentative: HTML-AAM Generic Role Verification Tests + + + + + + + + + +
  • x
  • + + + + + \ No newline at end of file From e49fe384d1e4c5e996b511f1d8bea9a4bfe6d322 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Sat, 7 Dec 2024 18:21:31 +0900 Subject: [PATCH 077/237] =?UTF-8?q?LibWeb:=20Align=20default=20=E2=80=9Cth?= =?UTF-8?q?=E2=80=9D=20and=20=E2=80=9Ctd=E2=80=9D=20roles=20with=20HTML-AA?= =?UTF-8?q?M=20spec=20and=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change aligns the default roles for “th” and “td” elements with the requirements in the HTML-AAM spec, and with the corresponding WPT tests at https://wpt.fyi/results/html-aam/table-roles.html, and with the behavior in other engines. Otherwise, without this change, the default role values for “th” and “td” elements in some cases don’t match the behavior in other engines, and don’t match the expected results for the corresponding WPT tests. --- .../LibWeb/HTML/HTMLTableCellElement.cpp | 35 +++-- .../wpt-import/html-aam/table-roles.txt | 12 ++ .../wpt-import/wai-aria/role/table-roles.txt | 14 ++ .../wpt-import/html-aam/table-roles.html | 53 +++++++ .../wpt-import/wai-aria/role/table-roles.html | 148 ++++++++++++++++++ 5 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/table-roles.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/table-roles.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/table-roles.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/wai-aria/role/table-roles.html diff --git a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp index a482a628d7007..a745ce363eda0 100644 --- a/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTableCellElement.cpp @@ -196,16 +196,31 @@ WebIDL::Long HTMLTableCellElement::cell_index() const Optional HTMLTableCellElement::default_role() const { - // TODO: For td: - // role=cell if the ancestor table element is exposed as a role=table - // role=gridcell if the ancestor table element is exposed as a role=grid or treegrid - // No corresponding role if the ancestor table element is not exposed as a role=table, grid or treegrid - // For th: - // role=columnheader, rowheader or cell if the ancestor table element is exposed as a role=table - // role=columnheader, rowheader or gridcell if the ancestor table element is exposed as a role=grid or treegrid - // No corresponding role if the ancestor table element is not exposed as a role=table, grid or treegrid - // https://www.w3.org/TR/html-aria/#el-td - // https://www.w3.org/TR/html-aria/#el-th + if (local_name() == TagNames::th) { + for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { + // AD-HOC: The ancestor checks here aren’t explicitly defined in the spec, but implicitly follow from what + // the spec does state, and from the physical placement/layout of elements. Also, the el-th and el-th-in-row + // tests at https://wpt.fyi/results/html-aam/table-roles.html require doing these ancestor checks — and + // implementing them causes the behavior to match that of other engines. + // https://w3c.github.io/html-aam/#el-th-columnheader + if (get_attribute(HTML::AttributeNames::scope) == "columnheader" || ancestor->local_name() == TagNames::thead) + return ARIA::Role::columnheader; + // https://w3c.github.io/html-aam/#el-th-rowheader + if (get_attribute(HTML::AttributeNames::scope) == "rowheader" || ancestor->local_name() == TagNames::tbody) + return ARIA::Role::rowheader; + } + } + auto const* table_element = first_ancestor_of_type(); + // https://w3c.github.io/html-aam/#el-td + // https://w3c.github.io/html-aam/#el-th/ + // (ancestor table element has table role) + if (table_element->role_or_default() == ARIA::Role::table) + return ARIA::Role::cell; + // https://w3c.github.io/html-aam/#el-td-gridcell + // https://w3c.github.io/html-aam/#el-th-gridcell + // (ancestor table element has grid or treegrid role) + if (first_is_one_of(table_element->role_or_default(), ARIA::Role::grid, ARIA::Role::gridcell)) + return ARIA::Role::gridcell; return {}; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/table-roles.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/table-roles.txt new file mode 100644 index 0000000000000..ee75a8ab8983e --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/table-roles.txt @@ -0,0 +1,12 @@ +Harness status: OK + +Found 7 tests + +7 Pass +Pass el-table +Pass el-caption +Pass el-tr-thead +Pass el-th +Pass el-tr-tbody +Pass el-th-in-row +Pass el-td \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/table-roles.txt b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/table-roles.txt new file mode 100644 index 0000000000000..6111915e656f7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/table-roles.txt @@ -0,0 +1,14 @@ +Harness status: OK + +Found 9 tests + +9 Pass +Pass div role is caption (in div with table role) +Pass orphan p role is caption +Pass span role is cell (in div with row role, in div with rowgroup role, in div with table role) +Pass orphan span role is cell +Pass span role is columnheader (in div with row role, in div with rowgroup role, in div with table role) +Pass div role is row (in div with rowgroup role, in div with table role) +Pass div role is rowgroup (in div with table role) +Pass role is rowheader (in div with row role, in div with rowgroup role, in div with table role) +Pass div role is table \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/table-roles.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/table-roles.html new file mode 100644 index 0000000000000..9ed64cd4f390a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/table-roles.html @@ -0,0 +1,53 @@ + + + + HTML-AAM Role Verification Tests + + + + + + + + + +

    Tests the computedrole mappings for the table-related roles defined in HTML-AAM. Most test names correspond to unique ID defined in the spec.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    caption
    abc
    123
    456
    xyz
    + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/table-roles.html b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/table-roles.html new file mode 100644 index 0000000000000..9ce6b33034907 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/table-roles.html @@ -0,0 +1,148 @@ + + + + Table Role Verification Tests + + + + + + + + +

    Tests table and related roles.

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    x
    +
    + +

    x

    + + +
    +
    +
    + x + x +
    +
    +
    +
    + x + x +
    +
    +
    + + x + + +
    +
    +
    + x + x + x +
    +
    +
    +
    + x + x + x +
    +
    +
    + + +
    +
    +
    + x +
    +
    +
    +
    + x +
    +
    +
    + + +
    +
    +
    + x + x +
    +
    +
    +
    + x + x +
    +
    +
    + + +
    +
    +
    + x + x + x +
    +
    +
    +
    + x + x + x + x +
    +
    +
    + + +
    +
    + x + x +
    +
    + x + x +
    +
    + + + + + \ No newline at end of file From afca902ea96ada3a50c19003f5e5604e68d21195 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Sun, 8 Dec 2024 17:16:04 +0900 Subject: [PATCH 078/237] Docs: Explain how to do Debug builds without optimizations on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change updates the (advanced) build docs to explain how to do a Debug build with the CXX `-O0` option set — which tells the compiler to build with no optimizations at all. Otherwise, Debug builds use the `-Og` option — which, when trying to check frame variables in a debugger can result in an error of this form: > error: Couldn't look up symbols: __ZN2AK6Detail10StringBaseD2Ev > Hint: The expression tried to call a function that is not present in > the target, perhaps because it was optimized out by > the compiler. --- Documentation/AdvancedBuildInstructions.md | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Documentation/AdvancedBuildInstructions.md b/Documentation/AdvancedBuildInstructions.md index ddb7f416cdf3d..11f21c5f23c56 100644 --- a/Documentation/AdvancedBuildInstructions.md +++ b/Documentation/AdvancedBuildInstructions.md @@ -96,3 +96,59 @@ export CCACHE_COMPILERCHECK="%compiler% -v" By default, ccache will include the plugins themselves in file hashes. So if a plugin changes, the hash of every file will change, and you will be stuck with an uncached build. This setting will prevent ccache from using plugins in the file hashes. + +## Debugging without any optimizations + +It’s possible that when trying to inspect certain frame variables in your debugger, you’ll get an error similar to the following: + +> error: Couldn't look up symbols: __ZN2AK6Detail10StringBaseD2Ev +> Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler. + +If you do run into such an error, the rest of this section explains how to deal with it. + +> [!WARNING] +> You probably only want to make the build-file change described below while you’re in the middle of examining the state of a particular build in your debugger — and then you’ll want to revert it after you’re done debugging. You otherwise probably don’t want to have the build-file change in place while you’re running WPT tests or in-tree tests and checking the results. + +1. At your command-line prompt in your shell environment, copy and paste the following: + + ```diff + $ patch -p1 < Date: Wed, 11 Dec 2024 13:04:13 -0500 Subject: [PATCH 079/237] LibWeb: Protect document observers from GC during observer invocation We currently (sometimes) copy the observer map to a vector for iteration to ensure we are not iterating over the map if the callback happens to remove the observer. But that list was not protected from GC. This patch ensures we protect that list, and makes all document observer notifiers protected from removal during iteration. --- Libraries/LibWeb/DOM/Document.cpp | 41 ++++++++++++++----------------- Libraries/LibWeb/DOM/Document.h | 16 ++++++++++++ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 6cbaa010782d7..46b39f01cc245 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -506,6 +506,7 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(m_scripts_to_execute_as_soon_as_possible); visitor.visit(m_node_iterators); visitor.visit(m_document_observers); + visitor.visit(m_document_observers_being_notified); visitor.visit(m_pending_scroll_event_targets); visitor.visit(m_pending_scrollend_event_targets); visitor.visit(m_resize_observers); @@ -2440,10 +2441,10 @@ void Document::update_readiness(HTML::DocumentReadyState readiness_value) } } - for (auto document_observer : m_document_observers) { - if (document_observer->document_readiness_observer()) - document_observer->document_readiness_observer()->function()(m_readiness); - } + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_readiness_observer(); + }, + m_readiness); } // https://html.spec.whatwg.org/multipage/dom.html#dom-document-lastmodified @@ -2507,11 +2508,9 @@ void Document::completely_finish_loading() return; ScopeGuard notify_observers = [this] { - auto observers_to_notify = m_document_observers.values(); - for (auto& document_observer : observers_to_notify) { - if (document_observer->document_completely_loaded()) - document_observer->document_completely_loaded()->function()(); - } + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_completely_loaded(); + }); }; // 1. Assert: document's browsing context is non-null. @@ -2777,10 +2776,10 @@ void Document::update_the_visibility_state(HTML::VisibilityState visibility_stat m_visibility_state = visibility_state; // 3. Run any page visibility change steps which may be defined in other specifications, with visibility state and document. - for (auto document_observer : m_document_observers) { - if (document_observer->document_visibility_state_observer()) - document_observer->document_visibility_state_observer()->function()(m_visibility_state); - } + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_visibility_state_observer(); + }, + m_visibility_state); // 4. Fire an event named visibilitychange at document, with its bubbles attribute initialized to true. auto event = DOM::Event::create(realm(), HTML::EventNames::visibilitychange); @@ -3090,10 +3089,10 @@ void Document::set_page_showing(bool page_showing) m_page_showing = page_showing; - for (auto document_observer : m_document_observers) { - if (document_observer->document_page_showing_observer()) - document_observer->document_page_showing_observer()->function()(m_page_showing); - } + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_page_showing_observer(); + }, + m_page_showing); } void Document::invalidate_stacking_context_tree() @@ -3773,11 +3772,9 @@ void Document::did_stop_being_active_document_in_navigable() { tear_down_layout_tree(); - auto observers_to_notify = m_document_observers.values(); - for (auto& document_observer : observers_to_notify) { - if (document_observer->document_became_inactive()) - document_observer->document_became_inactive()->function()(); - } + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_became_inactive(); + }); if (m_animation_driver_timer) m_animation_driver_timer->stop(); diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index c5d6ad140fcb9..261e2b620f75d 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -787,6 +787,21 @@ class Document void dispatch_events_for_animation_if_necessary(GC::Ref); + template + void notify_each_document_observer(GetNotifier&& get_notifier, Args&&... args) + { + ScopeGuard guard { [&]() { m_document_observers_being_notified.clear_with_capacity(); } }; + m_document_observers_being_notified.ensure_capacity(m_document_observers.size()); + + for (auto observer : m_document_observers) + m_document_observers_being_notified.unchecked_append(observer); + + for (auto document_observer : m_document_observers_being_notified) { + if (auto notifier = get_notifier(*document_observer)) + notifier->function()(forward(args)...); + } + } + GC::Ref m_page; OwnPtr m_style_computer; GC::Ptr m_style_sheets; @@ -897,6 +912,7 @@ class Document HashTable> m_node_iterators; HashTable> m_document_observers; + Vector> m_document_observers_being_notified; // https://html.spec.whatwg.org/multipage/dom.html#is-initial-about:blank bool m_is_initial_about_blank { false }; From a2419b5f4ea26777897bfc01bf2db4d735b5eee8 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 13:17:41 -0500 Subject: [PATCH 080/237] LibWeb: Add a document observer to be notified upon becoming active --- Libraries/LibWeb/DOM/Document.cpp | 4 ++++ Libraries/LibWeb/DOM/DocumentObserver.cpp | 9 +++++++++ Libraries/LibWeb/DOM/DocumentObserver.h | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 46b39f01cc245..d04c2066af656 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -3887,6 +3887,10 @@ void Document::make_active() navigable()->traversable_navigable()->page().client().page_did_finish_loading(url()); m_needs_to_call_page_did_load = false; } + + notify_each_document_observer([&](auto const& document_observer) { + return document_observer.document_became_active(); + }); } HTML::ListOfAvailableImages& Document::list_of_available_images() diff --git a/Libraries/LibWeb/DOM/DocumentObserver.cpp b/Libraries/LibWeb/DOM/DocumentObserver.cpp index 76b4c01828418..d5cee5da4f25c 100644 --- a/Libraries/LibWeb/DOM/DocumentObserver.cpp +++ b/Libraries/LibWeb/DOM/DocumentObserver.cpp @@ -23,6 +23,7 @@ void DocumentObserver::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_document); + visitor.visit(m_document_became_active); visitor.visit(m_document_became_inactive); visitor.visit(m_document_completely_loaded); visitor.visit(m_document_readiness_observer); @@ -36,6 +37,14 @@ void DocumentObserver::finalize() m_document->unregister_document_observer({}, *this); } +void DocumentObserver::set_document_became_active(Function callback) +{ + if (callback) + m_document_became_active = GC::create_function(vm().heap(), move(callback)); + else + m_document_became_active = nullptr; +} + void DocumentObserver::set_document_became_inactive(Function callback) { if (callback) diff --git a/Libraries/LibWeb/DOM/DocumentObserver.h b/Libraries/LibWeb/DOM/DocumentObserver.h index 5318d0453af11..9ba4ca2faebf2 100644 --- a/Libraries/LibWeb/DOM/DocumentObserver.h +++ b/Libraries/LibWeb/DOM/DocumentObserver.h @@ -20,6 +20,9 @@ class DocumentObserver final : public Bindings::PlatformObject { GC_DECLARE_ALLOCATOR(DocumentObserver); public: + [[nodiscard]] GC::Ptr> document_became_active() const { return m_document_became_active; } + void set_document_became_active(Function); + [[nodiscard]] GC::Ptr> document_became_inactive() const { return m_document_became_inactive; } void set_document_became_inactive(Function); @@ -42,6 +45,7 @@ class DocumentObserver final : public Bindings::PlatformObject { virtual void finalize() override; GC::Ref m_document; + GC::Ptr> m_document_became_active; GC::Ptr> m_document_became_inactive; GC::Ptr> m_document_completely_loaded; GC::Ptr> m_document_readiness_observer; From 68164aa7eca9e06a594f625497372a62b129b279 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 11 Dec 2024 13:18:21 -0500 Subject: [PATCH 081/237] LibWeb: Run the object representation task when the active state changes Currently, the following JS snippet will hang indefinitely: new DOMParser().parseFromString("", "text/html"); Because the document into which the object is inserted is not active. So the task queued to run the representation steps will never run. This patch implements the spec steps to rerun the representation steps when the active state changes, and avoid the hang when the object is created in an inactive document. --- Libraries/LibWeb/HTML/HTMLObjectElement.cpp | 20 + Libraries/LibWeb/HTML/HTMLObjectElement.h | 2 + .../wpt-import/html/semantics/interfaces.txt | 435 ++++++++++++++++++ .../wpt-import/html/semantics/interfaces.html | 74 +++ .../wpt-import/html/semantics/interfaces.js | 150 ++++++ 5 files changed, 681 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/interfaces.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.js diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp index 708ac29a620e1..aba1d2b115caa 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -54,12 +55,27 @@ void HTMLObjectElement::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLObjectElement); + + m_document_observer = realm.create(realm, document()); + + // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element + // Whenever one of the following conditions occur: + // - the element's node document changes whether it is fully active, + // ...the user agent must queue an element task on the DOM manipulation task source given the object element to run + // the following steps to (re)determine what the object element represents. + m_document_observer->set_document_became_active([this]() { + queue_element_task_to_run_object_representation_steps(); + }); + m_document_observer->set_document_became_inactive([this]() { + queue_element_task_to_run_object_representation_steps(); + }); } void HTMLObjectElement::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_resource_request); + visitor.visit(m_document_observer); } void HTMLObjectElement::form_associated_element_attribute_changed(FlyString const& name, Optional const&) @@ -186,6 +202,10 @@ bool HTMLObjectElement::has_ancestor_media_element_or_object_element_not_showing // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element:queue-an-element-task void HTMLObjectElement::queue_element_task_to_run_object_representation_steps() { + // AD-HOC: If the document isn't fully active, this task will never run, and we will indefinitely delay the load event. + if (!document().is_fully_active()) + return; + // This task being queued or actively running must delay the load event of the element's node document. m_document_load_event_delayer_for_object_representation_task.emplace(document()); diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.h b/Libraries/LibWeb/HTML/HTMLObjectElement.h index 4617ef7a4d5ca..acbe7c87777d7 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.h +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.h @@ -90,6 +90,8 @@ class HTMLObjectElement final GC::Ptr m_resource_request; + GC::Ptr m_document_observer; + Optional m_document_load_event_delayer_for_object_representation_task; Optional m_document_load_event_delayer_for_resource_load; }; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/interfaces.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/interfaces.txt new file mode 100644 index 0000000000000..65d5c0180382e --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/interfaces.txt @@ -0,0 +1,435 @@ +Harness status: OK + +Found 429 tests + +426 Pass +3 Fail +Pass Interfaces for a: useNS +Pass Interfaces for a: useParser +Pass Interfaces for A: createElement +Pass Interfaces for abbr: useNS +Pass Interfaces for abbr: useParser +Pass Interfaces for ABBR: createElement +Pass Interfaces for acronym: useNS +Pass Interfaces for acronym: useParser +Pass Interfaces for ACRONYM: createElement +Pass Interfaces for address: useNS +Pass Interfaces for address: useParser +Pass Interfaces for ADDRESS: createElement +Pass Interfaces for applet: useNS +Pass Interfaces for applet: useParser +Pass Interfaces for APPLET: createElement +Pass Interfaces for area: useNS +Pass Interfaces for area: useParser +Pass Interfaces for AREA: createElement +Pass Interfaces for article: useNS +Pass Interfaces for article: useParser +Pass Interfaces for ARTICLE: createElement +Pass Interfaces for aside: useNS +Pass Interfaces for aside: useParser +Pass Interfaces for ASIDE: createElement +Pass Interfaces for audio: useNS +Pass Interfaces for audio: useParser +Pass Interfaces for AUDIO: createElement +Pass Interfaces for b: useNS +Pass Interfaces for b: useParser +Pass Interfaces for B: createElement +Pass Interfaces for base: useNS +Pass Interfaces for base: useParser +Pass Interfaces for BASE: createElement +Pass Interfaces for basefont: useNS +Pass Interfaces for basefont: useParser +Pass Interfaces for BASEFONT: createElement +Pass Interfaces for bdi: useNS +Pass Interfaces for bdi: useParser +Pass Interfaces for BDI: createElement +Pass Interfaces for bdo: useNS +Pass Interfaces for bdo: useParser +Pass Interfaces for BDO: createElement +Pass Interfaces for bgsound: useNS +Pass Interfaces for bgsound: useParser +Pass Interfaces for BGSOUND: createElement +Pass Interfaces for big: useNS +Pass Interfaces for big: useParser +Pass Interfaces for BIG: createElement +Pass Interfaces for blink: useNS +Pass Interfaces for blink: useParser +Pass Interfaces for BLINK: createElement +Pass Interfaces for blockquote: useNS +Pass Interfaces for blockquote: useParser +Pass Interfaces for BLOCKQUOTE: createElement +Pass Interfaces for body: useNS +Pass Interfaces for body: useParser +Pass Interfaces for BODY: createElement +Pass Interfaces for br: useNS +Pass Interfaces for br: useParser +Pass Interfaces for BR: createElement +Pass Interfaces for button: useNS +Pass Interfaces for button: useParser +Pass Interfaces for BUTTON: createElement +Pass Interfaces for canvas: useNS +Pass Interfaces for canvas: useParser +Pass Interfaces for CANVAS: createElement +Pass Interfaces for caption: useNS +Pass Interfaces for CAPTION: createElement +Pass Interfaces for center: useNS +Pass Interfaces for center: useParser +Pass Interfaces for CENTER: createElement +Pass Interfaces for cite: useNS +Pass Interfaces for cite: useParser +Pass Interfaces for CITE: createElement +Pass Interfaces for code: useNS +Pass Interfaces for code: useParser +Pass Interfaces for CODE: createElement +Pass Interfaces for col: useNS +Pass Interfaces for COL: createElement +Pass Interfaces for colgroup: useNS +Pass Interfaces for COLGROUP: createElement +Pass Interfaces for command: useNS +Pass Interfaces for command: useParser +Pass Interfaces for COMMAND: createElement +Pass Interfaces for data: useNS +Pass Interfaces for data: useParser +Pass Interfaces for DATA: createElement +Pass Interfaces for datalist: useNS +Pass Interfaces for datalist: useParser +Pass Interfaces for DATALIST: createElement +Pass Interfaces for dd: useNS +Pass Interfaces for dd: useParser +Pass Interfaces for DD: createElement +Pass Interfaces for del: useNS +Pass Interfaces for del: useParser +Pass Interfaces for DEL: createElement +Pass Interfaces for details: useNS +Pass Interfaces for details: useParser +Pass Interfaces for DETAILS: createElement +Pass Interfaces for dfn: useNS +Pass Interfaces for dfn: useParser +Pass Interfaces for DFN: createElement +Pass Interfaces for dialog: useNS +Pass Interfaces for dialog: useParser +Pass Interfaces for DIALOG: createElement +Pass Interfaces for dir: useNS +Pass Interfaces for dir: useParser +Pass Interfaces for DIR: createElement +Pass Interfaces for directory: useNS +Pass Interfaces for directory: useParser +Pass Interfaces for DIRECTORY: createElement +Pass Interfaces for div: useNS +Pass Interfaces for div: useParser +Pass Interfaces for DIV: createElement +Pass Interfaces for dl: useNS +Pass Interfaces for dl: useParser +Pass Interfaces for DL: createElement +Pass Interfaces for dt: useNS +Pass Interfaces for dt: useParser +Pass Interfaces for DT: createElement +Pass Interfaces for em: useNS +Pass Interfaces for em: useParser +Pass Interfaces for EM: createElement +Pass Interfaces for embed: useNS +Pass Interfaces for embed: useParser +Pass Interfaces for EMBED: createElement +Pass Interfaces for fieldset: useNS +Pass Interfaces for fieldset: useParser +Pass Interfaces for FIELDSET: createElement +Pass Interfaces for figcaption: useNS +Pass Interfaces for figcaption: useParser +Pass Interfaces for FIGCAPTION: createElement +Pass Interfaces for figure: useNS +Pass Interfaces for figure: useParser +Pass Interfaces for FIGURE: createElement +Pass Interfaces for font: useNS +Pass Interfaces for font: useParser +Pass Interfaces for FONT: createElement +Pass Interfaces for foo-BAR: useNS +Pass Interfaces for foo-bar: useNS +Pass Interfaces for foo-bar: useParser +Pass Interfaces for FOO-BAR: createElement +Pass Interfaces for foo: useNS +Pass Interfaces for foo: useParser +Pass Interfaces for FOO: createElement +Pass Interfaces for footer: useNS +Pass Interfaces for footer: useParser +Pass Interfaces for FOOTER: createElement +Pass Interfaces for form: useNS +Pass Interfaces for form: useParser +Pass Interfaces for FORM: createElement +Pass Interfaces for frame: useNS +Pass Interfaces for FRAME: createElement +Pass Interfaces for frameset: useNS +Pass Interfaces for FRAMESET: createElement +Pass Interfaces for h1: useNS +Pass Interfaces for h1: useParser +Pass Interfaces for H1: createElement +Pass Interfaces for h2: useNS +Pass Interfaces for h2: useParser +Pass Interfaces for H2: createElement +Pass Interfaces for h3: useNS +Pass Interfaces for h3: useParser +Pass Interfaces for H3: createElement +Pass Interfaces for h4: useNS +Pass Interfaces for h4: useParser +Pass Interfaces for H4: createElement +Pass Interfaces for h5: useNS +Pass Interfaces for h5: useParser +Pass Interfaces for H5: createElement +Pass Interfaces for h6: useNS +Pass Interfaces for h6: useParser +Pass Interfaces for H6: createElement +Pass Interfaces for head: useNS +Pass Interfaces for head: useParser +Pass Interfaces for HEAD: createElement +Pass Interfaces for header: useNS +Pass Interfaces for header: useParser +Pass Interfaces for HEADER: createElement +Pass Interfaces for hgroup: useNS +Pass Interfaces for hgroup: useParser +Pass Interfaces for HGROUP: createElement +Pass Interfaces for hr: useNS +Pass Interfaces for hr: useParser +Pass Interfaces for HR: createElement +Pass Interfaces for html: useNS +Pass Interfaces for html: useParser +Pass Interfaces for HTML: createElement +Pass Interfaces for i: useNS +Pass Interfaces for i: useParser +Pass Interfaces for I: createElement +Pass Interfaces for iframe: useNS +Pass Interfaces for iframe: useParser +Pass Interfaces for IFRAME: createElement +Pass Interfaces for image: useNS +Pass Interfaces for IMAGE: createElement +Pass Interfaces for img: useNS +Pass Interfaces for img: useParser +Pass Interfaces for IMG: createElement +Pass Interfaces for input: useNS +Pass Interfaces for input: useParser +Pass Interfaces for INPUT: createElement +Pass Interfaces for ins: useNS +Pass Interfaces for ins: useParser +Pass Interfaces for INS: createElement +Pass Interfaces for isindex: useNS +Pass Interfaces for isindex: useParser +Pass Interfaces for ISINDEX: createElement +Pass Interfaces for kbd: useNS +Pass Interfaces for kbd: useParser +Pass Interfaces for KBD: createElement +Pass Interfaces for keygen: useNS +Pass Interfaces for keygen: useParser +Pass Interfaces for KEYGEN: createElement +Pass Interfaces for label: useNS +Pass Interfaces for label: useParser +Pass Interfaces for LABEL: createElement +Pass Interfaces for legend: useNS +Pass Interfaces for legend: useParser +Pass Interfaces for LEGEND: createElement +Pass Interfaces for li: useNS +Pass Interfaces for li: useParser +Pass Interfaces for LI: createElement +Pass Interfaces for link: useNS +Pass Interfaces for link: useParser +Pass Interfaces for LINK: createElement +Pass Interfaces for listing: useNS +Pass Interfaces for listing: useParser +Pass Interfaces for LISTING: createElement +Pass Interfaces for main: useNS +Pass Interfaces for main: useParser +Pass Interfaces for MAIN: createElement +Pass Interfaces for map: useNS +Pass Interfaces for map: useParser +Pass Interfaces for MAP: createElement +Pass Interfaces for mark: useNS +Pass Interfaces for mark: useParser +Pass Interfaces for MARK: createElement +Pass Interfaces for marquee: useNS +Pass Interfaces for marquee: useParser +Pass Interfaces for MARQUEE: createElement +Pass Interfaces for menu: useNS +Pass Interfaces for menu: useParser +Pass Interfaces for MENU: createElement +Pass Interfaces for meta: useNS +Pass Interfaces for meta: useParser +Pass Interfaces for META: createElement +Pass Interfaces for meter: useNS +Pass Interfaces for meter: useParser +Pass Interfaces for METER: createElement +Pass Interfaces for mod: useNS +Pass Interfaces for mod: useParser +Pass Interfaces for MOD: createElement +Pass Interfaces for multicol: useNS +Pass Interfaces for multicol: useParser +Pass Interfaces for MULTICOL: createElement +Pass Interfaces for nav: useNS +Pass Interfaces for nav: useParser +Pass Interfaces for NAV: createElement +Pass Interfaces for nextid: useNS +Pass Interfaces for nextid: useParser +Pass Interfaces for NEXTID: createElement +Pass Interfaces for nobr: useNS +Pass Interfaces for nobr: useParser +Pass Interfaces for NOBR: createElement +Pass Interfaces for noembed: useNS +Pass Interfaces for noembed: useParser +Pass Interfaces for NOEMBED: createElement +Pass Interfaces for noframes: useNS +Pass Interfaces for noframes: useParser +Pass Interfaces for NOFRAMES: createElement +Pass Interfaces for noscript: useNS +Pass Interfaces for noscript: useParser +Pass Interfaces for NOSCRIPT: createElement +Pass Interfaces for object: useNS +Pass Interfaces for object: useParser +Pass Interfaces for OBJECT: createElement +Pass Interfaces for ol: useNS +Pass Interfaces for ol: useParser +Pass Interfaces for OL: createElement +Pass Interfaces for optgroup: useNS +Pass Interfaces for optgroup: useParser +Pass Interfaces for OPTGROUP: createElement +Pass Interfaces for option: useNS +Pass Interfaces for option: useParser +Pass Interfaces for OPTION: createElement +Pass Interfaces for output: useNS +Pass Interfaces for output: useParser +Pass Interfaces for OUTPUT: createElement +Pass Interfaces for p: useNS +Pass Interfaces for p: useParser +Pass Interfaces for P: createElement +Pass Interfaces for param: useNS +Pass Interfaces for param: useParser +Pass Interfaces for PARAM: createElement +Fail Interfaces for permission: useNS +Fail Interfaces for permission: useParser +Fail Interfaces for PERMISSION: createElement +Pass Interfaces for picture: useNS +Pass Interfaces for picture: useParser +Pass Interfaces for PICTURE: createElement +Pass Interfaces for plaintext: useNS +Pass Interfaces for plaintext: useParser +Pass Interfaces for PLAINTEXT: createElement +Pass Interfaces for pre: useNS +Pass Interfaces for pre: useParser +Pass Interfaces for PRE: createElement +Pass Interfaces for progress: useNS +Pass Interfaces for progress: useParser +Pass Interfaces for PROGRESS: createElement +Pass Interfaces for q: useNS +Pass Interfaces for q: useParser +Pass Interfaces for Q: createElement +Pass Interfaces for quasit: useNS +Pass Interfaces for quasit: useParser +Pass Interfaces for QUASIT: createElement +Pass Interfaces for rb: useNS +Pass Interfaces for rb: useParser +Pass Interfaces for RB: createElement +Pass Interfaces for rp: useNS +Pass Interfaces for rp: useParser +Pass Interfaces for RP: createElement +Pass Interfaces for rt: useNS +Pass Interfaces for rt: useParser +Pass Interfaces for RT: createElement +Pass Interfaces for rtc: useNS +Pass Interfaces for rtc: useParser +Pass Interfaces for RTC: createElement +Pass Interfaces for ruby: useNS +Pass Interfaces for ruby: useParser +Pass Interfaces for RUBY: createElement +Pass Interfaces for s: useNS +Pass Interfaces for s: useParser +Pass Interfaces for S: createElement +Pass Interfaces for samp: useNS +Pass Interfaces for samp: useParser +Pass Interfaces for SAMP: createElement +Pass Interfaces for script: useNS +Pass Interfaces for script: useParser +Pass Interfaces for SCRIPT: createElement +Pass Interfaces for section: useNS +Pass Interfaces for section: useParser +Pass Interfaces for SECTION: createElement +Pass Interfaces for select: useNS +Pass Interfaces for select: useParser +Pass Interfaces for SELECT: createElement +Pass Interfaces for slot: useNS +Pass Interfaces for slot: useParser +Pass Interfaces for SLOT: createElement +Pass Interfaces for small: useNS +Pass Interfaces for small: useParser +Pass Interfaces for SMALL: createElement +Pass Interfaces for source: useNS +Pass Interfaces for source: useParser +Pass Interfaces for SOURCE: createElement +Pass Interfaces for spacer: useNS +Pass Interfaces for spacer: useParser +Pass Interfaces for SPACER: createElement +Pass Interfaces for span: useNS +Pass Interfaces for span: useParser +Pass Interfaces for SPAN: createElement +Pass Interfaces for strike: useNS +Pass Interfaces for strike: useParser +Pass Interfaces for STRIKE: createElement +Pass Interfaces for strong: useNS +Pass Interfaces for strong: useParser +Pass Interfaces for STRONG: createElement +Pass Interfaces for style: useNS +Pass Interfaces for style: useParser +Pass Interfaces for STYLE: createElement +Pass Interfaces for sub: useNS +Pass Interfaces for sub: useParser +Pass Interfaces for SUB: createElement +Pass Interfaces for summary: useNS +Pass Interfaces for summary: useParser +Pass Interfaces for SUMMARY: createElement +Pass Interfaces for sup: useNS +Pass Interfaces for sup: useParser +Pass Interfaces for SUP: createElement +Pass Interfaces for table: useNS +Pass Interfaces for table: useParser +Pass Interfaces for TABLE: createElement +Pass Interfaces for tbody: useNS +Pass Interfaces for TBODY: createElement +Pass Interfaces for td: useNS +Pass Interfaces for TD: createElement +Pass Interfaces for textarea: useNS +Pass Interfaces for textarea: useParser +Pass Interfaces for TEXTAREA: createElement +Pass Interfaces for tfoot: useNS +Pass Interfaces for TFOOT: createElement +Pass Interfaces for th: useNS +Pass Interfaces for TH: createElement +Pass Interfaces for thead: useNS +Pass Interfaces for THEAD: createElement +Pass Interfaces for time: useNS +Pass Interfaces for time: useParser +Pass Interfaces for TIME: createElement +Pass Interfaces for title: useNS +Pass Interfaces for title: useParser +Pass Interfaces for TITLE: createElement +Pass Interfaces for tr: useNS +Pass Interfaces for TR: createElement +Pass Interfaces for track: useNS +Pass Interfaces for track: useParser +Pass Interfaces for TRACK: createElement +Pass Interfaces for tt: useNS +Pass Interfaces for tt: useParser +Pass Interfaces for TT: createElement +Pass Interfaces for u: useNS +Pass Interfaces for u: useParser +Pass Interfaces for U: createElement +Pass Interfaces for ul: useNS +Pass Interfaces for ul: useParser +Pass Interfaces for UL: createElement +Pass Interfaces for var: useNS +Pass Interfaces for var: useParser +Pass Interfaces for VAR: createElement +Pass Interfaces for video: useNS +Pass Interfaces for video: useParser +Pass Interfaces for VIDEO: createElement +Pass Interfaces for wbr: useNS +Pass Interfaces for wbr: useParser +Pass Interfaces for WBR: createElement +Pass Interfaces for xmp: useNS +Pass Interfaces for xmp: useParser +Pass Interfaces for XMP: createElement +Pass Interfaces for å-bar: useNS +Pass Interfaces for Å-BAR: createElement \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.html new file mode 100644 index 0000000000000..dc6cfcffa681e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.html @@ -0,0 +1,74 @@ + + +Test of interfaces + + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.js new file mode 100644 index 0000000000000..05fc82f767358 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/interfaces.js @@ -0,0 +1,150 @@ +var elements = [ + ["a", "Anchor"], + ["abbr", ""], + ["acronym", ""], + ["address", ""], + ["applet", "Unknown"], + ["area", "Area"], + ["article", ""], + ["aside", ""], + ["audio", "Audio"], + ["b", ""], + ["base", "Base"], + ["basefont", ""], + ["bdi", ""], + ["bdo", ""], + ["bgsound", "Unknown"], + ["big", ""], + ["blink", "Unknown"], + ["blockquote", "Quote"], + ["body", "Body"], + ["br", "BR"], + ["button", "Button"], + ["canvas", "Canvas"], + ["caption", "TableCaption"], + ["center", ""], + ["cite", ""], + ["code", ""], + ["col", "TableCol"], + ["colgroup", "TableCol"], + ["command", "Unknown"], + ["data", "Data"], + ["datalist", "DataList"], + ["dd", ""], + ["del", "Mod"], + ["details", "Details"], + ["dfn", ""], + ["dialog", "Dialog"], + ["dir", "Directory"], + ["directory", "Unknown"], + ["div", "Div"], + ["dl", "DList"], + ["dt", ""], + ["em", ""], + ["embed", "Embed"], + ["fieldset", "FieldSet"], + ["figcaption", ""], + ["figure", ""], + ["font", "Font"], + ["foo-BAR", "Unknown"], // not a valid custom element name + ["foo-bar", ""], // valid custom element name + ["foo", "Unknown"], + ["footer", ""], + ["form", "Form"], + ["frame", "Frame"], + ["frameset", "FrameSet"], + ["h1", "Heading"], + ["h2", "Heading"], + ["h3", "Heading"], + ["h4", "Heading"], + ["h5", "Heading"], + ["h6", "Heading"], + ["head", "Head"], + ["header", ""], + ["hgroup", ""], + ["hr", "HR"], + ["html", "Html"], + ["i", ""], + ["iframe", "IFrame"], + ["image", "Unknown"], + ["img", "Image"], + ["input", "Input"], + ["ins", "Mod"], + ["isindex", "Unknown"], + ["kbd", ""], + ["keygen", "Unknown"], + ["label", "Label"], + ["legend", "Legend"], + ["li", "LI"], + ["link", "Link"], + ["listing", "Pre"], + ["main", ""], + ["map", "Map"], + ["mark", ""], + ["marquee", "Marquee"], + ["menu", "Menu"], + ["meta", "Meta"], + ["meter", "Meter"], + ["mod", "Unknown"], + ["multicol", "Unknown"], + ["nav", ""], + ["nextid", "Unknown"], + ["nobr", ""], + ["noembed", ""], + ["noframes", ""], + ["noscript", ""], + ["object", "Object"], + ["ol", "OList"], + ["optgroup", "OptGroup"], + ["option", "Option"], + ["output", "Output"], + ["p", "Paragraph"], + ["param", "Param"], + ["permission", "Permission"], + ["picture", "Picture"], + ["plaintext", ""], + ["pre", "Pre"], + ["progress", "Progress"], + ["q", "Quote"], + ["quasit", "Unknown"], + ["rb", ""], + ["rp", ""], + ["rt", ""], + ["rtc", ""], + ["ruby", ""], + ["s", ""], + ["samp", ""], + ["script", "Script"], + ["section", ""], + ["select", "Select"], + ["slot", "Slot"], + ["small", ""], + ["source", "Source"], + ["spacer", "Unknown"], + ["span", "Span"], + ["strike", ""], + ["strong", ""], + ["style", "Style"], + ["sub", ""], + ["summary", ""], + ["sup", ""], + ["table", "Table"], + ["tbody", "TableSection"], + ["td", "TableCell"], + ["textarea", "TextArea"], + ["tfoot", "TableSection"], + ["th", "TableCell"], + ["thead", "TableSection"], + ["time", "Time"], + ["title", "Title"], + ["tr", "TableRow"], + ["track", "Track"], + ["tt", ""], + ["u", ""], + ["ul", "UList"], + ["var", ""], + ["video", "Video"], + ["wbr", ""], + ["xmp", "Pre"], + ["\u00E5-bar", "Unknown"], // not a valid custom element name +]; From 96540e9f8984c782a4ea650672243cbd7172abf0 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Mon, 9 Dec 2024 11:41:14 +0900 Subject: [PATCH 082/237] =?UTF-8?q?LibWeb:=20Add=20support=20for=20the=20?= =?UTF-8?q?=E2=80=9Csuggestion=E2=80=9D=20ARIA=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/LibWeb/ARIA/AriaRoles.json | 42 +++++++++++++++++++ Libraries/LibWeb/ARIA/RoleType.cpp | 2 + Libraries/LibWeb/ARIA/Roles.cpp | 1 + Libraries/LibWeb/ARIA/Roles.h | 1 + .../wpt-import/wai-aria/role/roles.txt | 5 +-- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/ARIA/AriaRoles.json b/Libraries/LibWeb/ARIA/AriaRoles.json index e3af331929ddf..93f423cb12df8 100644 --- a/Libraries/LibWeb/ARIA/AriaRoles.json +++ b/Libraries/LibWeb/ARIA/AriaRoles.json @@ -2046,6 +2046,48 @@ "childrenArePresentational": false, "implicitValueForRole": {} }, + "Suggestion": { + "specLink": "https://w3c.github.io/aria/#suggestion", + "description": "A single proposed change to content.", + "superClassRoles": [ + "Section" + ], + "supportedStates": [ + "aria-busy", + "aria-current", + "aria-errormessage", + "aria-grabbed", + "aria-haspopup", + "aria-hidden", + "aria-invalid" + ], + "supportedProperties": [ + "aria-atomic", + "aria-brailleroledescription", + "aria-controls", + "aria-describedby", + "aria-description", + "aria-details", + "aria-dropeffect", + "aria-flowto", + "aria-haspopup", + "aria-keyshortcuts", + "aria-live", + "aria-owns", + "aria-relevant", + "aria-roledescription" + ], + "requiredStates": [], + "requiredProperties": [], + "prohibitedStates": [], + "prohibitedProperties": [], + "requiredContextRoles": [], + "requiredOwnedElements": [], + "nameFromSource": "Prohibited", + "accessibleNameRequired": false, + "childrenArePresentational": false, + "implicitValueForRole": {} + }, "Superscript": { "specLink": "https://www.w3.org/TR/wai-aria-1.2/#superscript", "description": "One or more superscripted characters. See related superscript.", diff --git a/Libraries/LibWeb/ARIA/RoleType.cpp b/Libraries/LibWeb/ARIA/RoleType.cpp index ccf32da5e13a7..c6e888e4139db 100644 --- a/Libraries/LibWeb/ARIA/RoleType.cpp +++ b/Libraries/LibWeb/ARIA/RoleType.cpp @@ -293,6 +293,8 @@ ErrorOr> RoleType::build_role_object(Role role, bool foc return adopt_nonnull_own_or_enomem(new (nothrow) Strong(data)); case Role::subscript: return adopt_nonnull_own_or_enomem(new (nothrow) Subscript(data)); + case Role::suggestion: + return adopt_nonnull_own_or_enomem(new (nothrow) Suggestion(data)); case Role::superscript: return adopt_nonnull_own_or_enomem(new (nothrow) Superscript(data)); case Role::switch_: diff --git a/Libraries/LibWeb/ARIA/Roles.cpp b/Libraries/LibWeb/ARIA/Roles.cpp index 4030acd960dde..a1fa17c79e717 100644 --- a/Libraries/LibWeb/ARIA/Roles.cpp +++ b/Libraries/LibWeb/ARIA/Roles.cpp @@ -131,6 +131,7 @@ bool is_document_structure_role(Role role) Role::separator, // TODO: Only when not focusable Role::strong, Role::subscript, + Role::suggestion, Role::superscript, Role::table, Role::term, diff --git a/Libraries/LibWeb/ARIA/Roles.h b/Libraries/LibWeb/ARIA/Roles.h index b8562d2560880..ac3e4f4982998 100644 --- a/Libraries/LibWeb/ARIA/Roles.h +++ b/Libraries/LibWeb/ARIA/Roles.h @@ -89,6 +89,7 @@ namespace Web::ARIA { __ENUMERATE_ARIA_ROLE(strong) \ __ENUMERATE_ARIA_ROLE(structure) \ __ENUMERATE_ARIA_ROLE(subscript) \ + __ENUMERATE_ARIA_ROLE(suggestion) \ __ENUMERATE_ARIA_ROLE(superscript) \ __ENUMERATE_ARIA_ROLE(switch_) \ __ENUMERATE_ARIA_ROLE(tab) \ diff --git a/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt index cbed5d0d60e17..8afec199864dd 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/roles.txt @@ -2,8 +2,7 @@ Harness status: OK Found 54 tests -53 Pass -1 Fail +54 Pass Pass role: alert Pass role: alertdialog Pass role: application @@ -49,7 +48,7 @@ Pass role: spinbutton Pass role: status Pass role: strong Pass role: subscript -Fail role: suggestion +Pass role: suggestion Pass role: superscript Pass role: switch Pass role: term From 6e24f23aa09c89bc0cd171fe69df96d276d8d759 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Sun, 8 Dec 2024 15:53:19 +0900 Subject: [PATCH 083/237] =?UTF-8?q?LibWeb:=20Add=20support=20for=20the=20?= =?UTF-8?q?=E2=80=9Cmark=E2=80=9D=20ARIA=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/LibWeb/ARIA/AriaRoles.json | 42 +++++++++++++++++++++++++++ Libraries/LibWeb/ARIA/RoleType.cpp | 2 ++ Libraries/LibWeb/ARIA/Roles.h | 1 + Libraries/LibWeb/HTML/HTMLElement.cpp | 3 ++ 4 files changed, 48 insertions(+) diff --git a/Libraries/LibWeb/ARIA/AriaRoles.json b/Libraries/LibWeb/ARIA/AriaRoles.json index 93f423cb12df8..4bd5ec3453896 100644 --- a/Libraries/LibWeb/ARIA/AriaRoles.json +++ b/Libraries/LibWeb/ARIA/AriaRoles.json @@ -3307,6 +3307,48 @@ "childrenArePresentational": false, "implicitValueForRole": {} }, + "Mark": { + "specLink": "https://w3c.github.io/aria/#mark", + "description": "Content which is marked or highlighted for reference or notation purposes, due to the content's relevance in the enclosing context.", + "superClassRoles": [ + "Section" + ], + "supportedStates": [ + "aria-busy", + "aria-current", + "aria-disabled", + "aria-grabbed", + "aria-hidden", + "aria-invalid" + ], + "supportedProperties": [ + "aria-atomic", + "aria-brailleroledescription", + "aria-controls", + "aria-describedby", + "aria-description", + "aria-details", + "aria-dropeffect", + "aria-errormessage", + "aria-flowto", + "aria-haspopup", + "aria-keyshortcuts", + "aria-live", + "aria-owns", + "aria-relevant", + "aria-roledescription" + ], + "requiredStates": [], + "requiredProperties": [], + "prohibitedStates": [], + "prohibitedProperties": [], + "requiredContextRoles": [], + "requiredOwnedElements": [], + "nameFromSource": "Prohibited", + "accessibleNameRequired": false, + "childrenArePresentational": false, + "implicitValueForRole": {} + }, "Navigation": { "specLink": "https://www.w3.org/TR/wai-aria-1.2/#navigation", "description": "A landmark containing a collection of navigational elements (usually links) for navigating the document or related documents.", diff --git a/Libraries/LibWeb/ARIA/RoleType.cpp b/Libraries/LibWeb/ARIA/RoleType.cpp index c6e888e4139db..d1a390404928b 100644 --- a/Libraries/LibWeb/ARIA/RoleType.cpp +++ b/Libraries/LibWeb/ARIA/RoleType.cpp @@ -232,6 +232,8 @@ ErrorOr> RoleType::build_role_object(Role role, bool foc return adopt_nonnull_own_or_enomem(new (nothrow) Main(data)); case Role::marquee: return adopt_nonnull_own_or_enomem(new (nothrow) Marquee(data)); + case Role::mark: + return adopt_nonnull_own_or_enomem(new (nothrow) Mark(data)); case Role::math: return adopt_nonnull_own_or_enomem(new (nothrow) Math(data)); case Role::meter: diff --git a/Libraries/LibWeb/ARIA/Roles.h b/Libraries/LibWeb/ARIA/Roles.h index ac3e4f4982998..96f5d49ecb49d 100644 --- a/Libraries/LibWeb/ARIA/Roles.h +++ b/Libraries/LibWeb/ARIA/Roles.h @@ -53,6 +53,7 @@ namespace Web::ARIA { __ENUMERATE_ARIA_ROLE(listitem) \ __ENUMERATE_ARIA_ROLE(log) \ __ENUMERATE_ARIA_ROLE(main) \ + __ENUMERATE_ARIA_ROLE(mark) \ __ENUMERATE_ARIA_ROLE(marquee) \ __ENUMERATE_ARIA_ROLE(math) \ __ENUMERATE_ARIA_ROLE(meter) \ diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 001ee6d104c00..cb3a9c62ea071 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -776,6 +776,9 @@ Optional HTMLElement::default_role() const // https://www.w3.org/TR/html-aria/#el-main if (local_name() == TagNames::main) return ARIA::Role::main; + // https://www.w3.org/TR/html-aria/#el-mark + if (local_name() == TagNames::mark) + return ARIA::Role::mark; // https://www.w3.org/TR/html-aria/#el-nav if (local_name() == TagNames::nav) return ARIA::Role::navigation; From ccbc436e8565cafbf5976e1bf14be64044bbd495 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Sun, 8 Dec 2024 22:29:09 +0900 Subject: [PATCH 084/237] =?UTF-8?q?LibWeb:=20Support=20the=20=E2=80=9Cimag?= =?UTF-8?q?e=E2=80=9D=20synonym=20for=20the=20=E2=80=9Cimg=E2=80=9D=20ARIA?= =?UTF-8?q?=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Additionally: For “img” elements with empty “alt” attributes, change the default role to the newer, preferred “none” synonym for the older “presentation” role; import https://wpt.fyi/results/html-aam/roles.html (which provides test/regression coverage for these changes). --- Libraries/LibWeb/ARIA/RoleType.cpp | 2 + Libraries/LibWeb/ARIA/Roles.h | 1 + Libraries/LibWeb/HTML/HTMLImageElement.cpp | 9 +- .../expected/wpt-import/html-aam/roles.txt | 63 ++++++ .../Text/input/wpt-import/html-aam/roles.html | 209 ++++++++++++++++++ 5 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles.html diff --git a/Libraries/LibWeb/ARIA/RoleType.cpp b/Libraries/LibWeb/ARIA/RoleType.cpp index d1a390404928b..ceba11dc0fcdc 100644 --- a/Libraries/LibWeb/ARIA/RoleType.cpp +++ b/Libraries/LibWeb/ARIA/RoleType.cpp @@ -210,6 +210,8 @@ ErrorOr> RoleType::build_role_object(Role role, bool foc return adopt_nonnull_own_or_enomem(new (nothrow) Group(data)); case Role::heading: return adopt_nonnull_own_or_enomem(new (nothrow) Heading(data)); + case Role::image: + return adopt_nonnull_own_or_enomem(new (nothrow) Img(data)); case Role::img: return adopt_nonnull_own_or_enomem(new (nothrow) Img(data)); case Role::input: diff --git a/Libraries/LibWeb/ARIA/Roles.h b/Libraries/LibWeb/ARIA/Roles.h index 96f5d49ecb49d..1ce01d8cc870b 100644 --- a/Libraries/LibWeb/ARIA/Roles.h +++ b/Libraries/LibWeb/ARIA/Roles.h @@ -43,6 +43,7 @@ namespace Web::ARIA { __ENUMERATE_ARIA_ROLE(gridcell) \ __ENUMERATE_ARIA_ROLE(group) \ __ENUMERATE_ARIA_ROLE(heading) \ + __ENUMERATE_ARIA_ROLE(image) \ __ENUMERATE_ARIA_ROLE(img) \ __ENUMERATE_ARIA_ROLE(input) \ __ENUMERATE_ARIA_ROLE(insertion) \ diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Libraries/LibWeb/HTML/HTMLImageElement.cpp index cd6acf934cba7..2d5207c1aff1a 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -411,10 +411,15 @@ Optional HTMLImageElement::default_role() const { // https://www.w3.org/TR/html-aria/#el-img // https://www.w3.org/TR/html-aria/#el-img-no-alt + // https://w3c.github.io/aria/#image + // NOTE: The "image" role value is a synonym for the older "img" role value; however, the el-img test in + // https://wpt.fyi/results/html-aam/roles.html expects the value to be "image" (not "img"). if (!alt().is_empty()) - return ARIA::Role::img; + return ARIA::Role::image; // https://www.w3.org/TR/html-aria/#el-img-empty-alt - return ARIA::Role::presentation; + // NOTE: The "none" role value is a synonym for the older "presentation" role value; however, the el-img-alt-no-value + // test in https://wpt.fyi/results/html-aam/roles.html expects the value to be "none" (not "presentation"). + return ARIA::Role::none; } // https://html.spec.whatwg.org/multipage/images.html#use-srcset-or-picture diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.txt new file mode 100644 index 0000000000000..d62e2a2cf66b5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles.txt @@ -0,0 +1,63 @@ +Harness status: OK + +Found 58 tests + +58 Pass +Pass el-address +Pass el-article +Pass el-blockquote +Pass el-button +Pass el-code +Pass el-dd +Pass el-del +Pass el-details +Pass el-dfn +Pass el-dt +Pass el-em +Pass el-fieldset +Pass el-figure +Pass el-form +Pass el-h1 +Pass el-h2 +Pass el-h3 +Pass el-h4 +Pass el-h5 +Pass el-h6 +Pass el-hgroup +Pass el-hr +Pass el-img +Pass el-input-button +Pass el-input-checkbox +Pass el-input-email +Pass el-input-radio +Pass el-input-range +Pass el-input-reset +Pass el-input-search +Pass el-input-submit +Pass el-input-tel +Pass el-input-text +Pass el-input-url +Pass el-ins +Pass el-li-in-ul +Pass el-li-in-ol +Pass el-main +Pass el-mark +Pass el-menu +Pass el-meter +Pass el-nav +Pass el-ol +Pass el-option +Pass el-output +Pass el-p +Pass el-progress +Pass el-s +Pass el-search +Pass el-select-listbox +Pass el-strong +Pass el-sub +Pass el-sup +Pass el-time +Pass el-textarea +Pass el-ul +Pass el-img-alt-no-value +Pass el-img-empty-alt \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.html new file mode 100644 index 0000000000000..e6dcc1a649568 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles.html @@ -0,0 +1,209 @@ + + + + HTML-AAM Role Verification Tests + + + + + + + + + + +

    Tests the computedrole mappings defined in HTML-AAM. Most test names correspond to a unique ID defined in the spec.

    + +

    These should remain in alphabetical order, and include all HTML tagnames. If a tag is not tested here, include a pointer to the file where it is tested, such as: <!-- caption -> ./table-roles.html -->

    + + + + +
    x
    + +
    x
    + + + + + + + +
    x
    + + + + + + +x + + + + + + +
    +
    x
    + +
    x
    +
    + +x +
    xx
    +x + + + + + +
    + +
    x
    +
    x
    +
    + +x + +
    x
    + +
    x
    x
    + +
    + + + +

    x

    +

    x

    +

    x

    +

    x

    +
    x
    +
    x
    + + + + +

    x

    +
    + + + +x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +x + + + + + + +
    • x
    • x
    +
    1. x
    2. x
    + + +
    x
    + +x + +
  • x
  • + +x + + + +
    1. x
    2. x
    + + + + + + +x +

    x

    + + + +x + + + + +x + + +x + + + + + + + + + + + +x + +x + +x + + + + + + + + + + + + + + +
    • x
    • x
    + + + + + + + + From 50e7e9f58dd19ce48b1e8002155742dc1778dffe Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Sun, 8 Dec 2024 15:54:31 +0900 Subject: [PATCH 085/237] =?UTF-8?q?LibWeb:=20Assign=20=E2=80=9Cdir?= =?UTF-8?q?=E2=80=9D,=20=E2=80=9Cdd=E2=80=9D,=20=E2=80=9Cdt=E2=80=9D=20def?= =?UTF-8?q?ault=20ARIA=20roles=20per-spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, import http://wpt.live/html-aam/dir-role.tentative.html (which provides test/regression coverage for the “dir” change). --- Libraries/LibWeb/HTML/HTMLElement.cpp | 9 ++++++ .../html-aam/dir-role.tentative.txt | 6 ++++ .../html-aam/dir-role.tentative.html | 28 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/dir-role.tentative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/dir-role.tentative.html diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index cb3a9c62ea071..9428792102fa4 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -740,6 +740,15 @@ Optional HTMLElement::default_role() const // https://www.w3.org/TR/html-aria/#el-code if (local_name() == TagNames::code) return ARIA::Role::code; + // https://w3c.github.io/html-aam/#el-dd + if (local_name() == TagNames::dd) + return ARIA::Role::definition; + // https://wpt.fyi/results/html-aam/dir-role.tentative.html + if (local_name() == TagNames::dir) + return ARIA::Role::list; + // https://w3c.github.io/html-aam/#el-dt + if (local_name() == TagNames::dt) + return ARIA::Role::term; // https://www.w3.org/TR/html-aria/#el-dfn if (local_name() == TagNames::dfn) return ARIA::Role::term; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/dir-role.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/dir-role.tentative.txt new file mode 100644 index 0000000000000..4156b9fd290e2 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/dir-role.tentative.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass el-dir \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/dir-role.tentative.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/dir-role.tentative.html new file mode 100644 index 0000000000000..cc80c89969d20 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/dir-role.tentative.html @@ -0,0 +1,28 @@ + + + + HTML-AAM dir Element Role Verification Test + + + + + + + + + + +

    Tentative test for the expected role mappings of the obsolete dir element. + The computedrole mappings are defined in HTML-AAM.

    + +

    Merge the outcome of this test into roles.html in the appropriate alphabetical order, when it is no longer considered tentative:

    + +
  • x
  • x
  • + + + + + + From 7d2a037d03ca4c3292d549652119aa605a898f6a Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Thu, 12 Dec 2024 10:24:50 +0000 Subject: [PATCH 086/237] LibWeb: Align `execute_async_script` variable name with the latest spec --- Libraries/LibWeb/WebDriver/ExecuteScript.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/WebDriver/ExecuteScript.cpp b/Libraries/LibWeb/WebDriver/ExecuteScript.cpp index f7220171544c6..fd8a3ce1e5e2e 100644 --- a/Libraries/LibWeb/WebDriver/ExecuteScript.cpp +++ b/Libraries/LibWeb/WebDriver/ExecuteScript.cpp @@ -141,6 +141,7 @@ void execute_script(HTML::BrowsingContext const& browsing_context, ByteString bo WebIDL::react_to_promise(promise, reaction_steps, reaction_steps); } +// https://w3c.github.io/webdriver/#execute-async-script void execute_async_script(HTML::BrowsingContext const& browsing_context, ByteString body, GC::MarkedVector arguments, Optional const& timeout_ms, GC::Ref on_complete) { auto const* document = browsing_context.active_document(); @@ -175,8 +176,7 @@ void execute_async_script(HTML::BrowsingContext const& browsing_context, ByteStr // 2. Append resolvingFunctions.[[Resolve]] to arguments. arguments.append(resolving_functions.resolve); - // 3. Let result be the result of calling execute a function body, with arguments body and arguments. - // FIXME: 'result' -> 'scriptResult' (spec issue) + // 3. Let scriptResult be the result of calling execute a function body, with arguments body and arguments. auto script_result = execute_a_function_body(browsing_context, body, move(arguments)); // 4. If scriptResult.[[Type]] is not normal, then reject promise with value scriptResult.[[Value]], and abort these steps. From 32dddc81400e988c826c4696f6843e7fd4a4ec28 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Thu, 12 Dec 2024 20:42:20 +0900 Subject: [PATCH 087/237] Tests: Import some ARIA and HTML-AAM (regression) tests (no code) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re passing all the tests in this patch on trunk — so let’s go ahead and welcome them into our regression-testing garden. --- .../expected/wpt-import/html-aam/names.txt | 133 +++++++++ .../wpt-import/html-aam/roles-generic.txt | 17 ++ .../wpt-import/wai-aria/role/menu-roles.txt | 17 ++ .../Text/input/wpt-import/html-aam/names.html | 260 ++++++++++++++++++ .../wpt-import/html-aam/roles-generic.html | 42 +++ .../wpt-import/wai-aria/role/menu-roles.html | 63 +++++ 6 files changed, 532 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/names.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/menu-roles.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/names.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/wai-aria/role/menu-roles.html diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/names.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/names.txt new file mode 100644 index 0000000000000..3a395ca4dede8 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/names.txt @@ -0,0 +1,133 @@ +Harness status: OK + +Found 128 tests + +128 Pass +Pass address no name +Pass address aria-label +Pass address aria-labelledby +Pass address title +Pass address aria-labelledby vs aria-label vs title +Pass address aria-labelledby vs aria-label +Pass address aria-labelledby vs title +Pass address aria-label vs title +Pass aside no name +Pass aside aria-label +Pass aside aria-labelledby +Pass aside title +Pass aside aria-labelledby vs aria-label vs title +Pass aside aria-labelledby vs aria-label +Pass aside aria-labelledby vs title +Pass aside aria-label vs title +Pass blockquote no name +Pass blockquote aria-label +Pass blockquote aria-labelledby +Pass blockquote title +Pass blockquote aria-labelledby vs aria-label vs title +Pass blockquote aria-labelledby vs aria-label +Pass blockquote aria-labelledby vs title +Pass blockquote aria-label vs title +Pass details no name +Pass details aria-label +Pass details aria-labelledby +Pass details title +Pass details aria-labelledby vs aria-label vs title +Pass details aria-labelledby vs aria-label +Pass details aria-labelledby vs title +Pass details aria-label vs title +Pass figure no name +Pass figure aria-label +Pass figure aria-labelledby +Pass figure title +Pass figure aria-labelledby vs aria-label vs title +Pass figure aria-labelledby vs aria-label +Pass figure aria-labelledby vs title +Pass figure aria-label vs title +Pass footer no name +Pass footer aria-label +Pass footer aria-labelledby +Pass footer title +Pass footer aria-labelledby vs aria-label vs title +Pass footer aria-labelledby vs aria-label +Pass footer aria-labelledby vs title +Pass footer aria-label vs title +Pass form no name +Pass form aria-label +Pass form aria-labelledby +Pass form title +Pass form aria-labelledby vs aria-label vs title +Pass form aria-labelledby vs aria-label +Pass form aria-labelledby vs title +Pass form aria-label vs title +Pass hgroup no name +Pass hgroup aria-label +Pass hgroup aria-labelledby +Pass hgroup title +Pass hgroup aria-labelledby vs aria-label vs title +Pass hgroup aria-labelledby vs aria-label +Pass hgroup aria-labelledby vs title +Pass hgroup aria-label vs title +Pass hr no name +Pass hr aria-label +Pass hr aria-labelledby +Pass hr title +Pass hr aria-labelledby vs aria-label vs title +Pass hr aria-labelledby vs aria-label +Pass hr aria-labelledby vs title +Pass hr aria-label vs title +Pass ol no name +Pass ol aria-label +Pass ol aria-labelledby +Pass ol title +Pass ol aria-labelledby vs aria-label vs title +Pass ol aria-labelledby vs aria-label +Pass ol aria-labelledby vs title +Pass ol aria-label vs title +Pass main no name +Pass main aria-label +Pass main aria-labelledby +Pass main title +Pass main aria-labelledby vs aria-label vs title +Pass main aria-labelledby vs aria-label +Pass main aria-labelledby vs title +Pass main aria-label vs title +Pass menu no name +Pass menu aria-label +Pass menu aria-labelledby +Pass menu title +Pass menu aria-labelledby vs aria-label vs title +Pass menu aria-labelledby vs aria-label +Pass menu aria-labelledby vs title +Pass menu aria-label vs title +Pass nav no name +Pass nav aria-label +Pass nav aria-labelledby +Pass nav title +Pass nav aria-labelledby vs aria-label vs title +Pass nav aria-labelledby vs aria-label +Pass nav aria-labelledby vs title +Pass nav aria-label vs title +Pass search no name +Pass search aria-label +Pass search aria-labelledby +Pass search title +Pass search aria-labelledby vs aria-label vs title +Pass search aria-labelledby vs aria-label +Pass search aria-labelledby vs title +Pass search aria-label vs title +Pass section no name +Pass section aria-label +Pass section aria-labelledby +Pass section title +Pass section aria-labelledby vs aria-label vs title +Pass section aria-labelledby vs aria-label +Pass section aria-labelledby vs title +Pass section aria-label vs title +Pass ul no name +Pass ul aria-label +Pass ul aria-labelledby +Pass ul title +Pass ul aria-labelledby vs aria-label vs title +Pass ul aria-labelledby vs aria-label +Pass ul aria-labelledby vs title +Pass ul aria-label vs title \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.txt new file mode 100644 index 0000000000000..d00ea1c6499fe --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-generic.txt @@ -0,0 +1,17 @@ +Harness status: OK + +Found 12 tests + +12 Pass +Pass el-b +Pass el-bdi +Pass el-bdo +Pass el-data +Pass el-div +Pass el-i +Pass el-pre +Pass el-q +Pass el-samp +Pass el-small +Pass el-span +Pass el-u \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/menu-roles.txt b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/menu-roles.txt new file mode 100644 index 0000000000000..7312ae522577d --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/wai-aria/role/menu-roles.txt @@ -0,0 +1,17 @@ +Harness status: OK + +Found 12 tests + +12 Pass +Pass role is menu +Pass role is menuitem (in menu) +Pass role is group (in menu) +Pass role is menuitem (in group, in menu) +Pass role is menuitemradio (in group, in menu) +Pass role is menuitemcheckbox (in group, in menu) +Pass role is menubar +Pass role is menuitem (in menubar) +Pass role is group (in menubar) +Pass role is menuitem (in group, in menubar) +Pass role is menuitemradio (in group, in menubar) +Pass role is menuitemcheckbox (in group, in menubar) \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/names.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/names.html new file mode 100644 index 0000000000000..11f1dd3b76b7c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/names.html @@ -0,0 +1,260 @@ + + + + HTML-AAM Element Accessible Name From Author Tests + + + + + + + + + + +

    Tests the accName for elements defined in HTML-AAM: Accessible Name Computations By HTML Element. + These tests are meant to show whether an element returns a name per the naming mechanism used. See wpt: accname for expanded accName testing.

    + + + + + +

    address element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + + +

    aside element

    + + + + + + + + + + + + + +

    blockquote element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + + +

    details element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    figure element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    footer element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    form element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    hgroup element

    +

    x

    y

    + +

    x

    y

    +

    x

    y

    +

    x

    y

    + +

    x

    y

    +

    x

    y

    + +

    x

    y

    +

    x

    y

    + + +

    hr element

    +
    + +
    +
    +
    + +
    +
    + +
    +
    + + +

    ol element

    +
    1. x
    + +
    1. x
    +
    1. x
    +
    1. x
    + +
    1. x
    +
    1. x
    + +
    1. x
    +
    1. x
    + + +

    main element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    menu element

    +
  • x
  • + +
  • x
  • +
  • x
  • +
  • x
  • + +
  • x
  • +
  • x
  • + +
  • x
  • +
  • x
  • + + +

    nav element

    + + + + + + + + + + + + + +

    search element

    +x + +x +x +x + +x +x + +x +x + + +

    section element

    +
    x
    + +
    x
    +
    x
    +
    x
    + +
    x
    +
    x
    + +
    x
    +
    x
    + + +

    ul element

    +
    • x
    + +
    • x
    +
    • x
    +
    • x
    + +
    • x
    +
    • x
    + +
    • x
    +
    • x
    + + + +
    labelledby
    + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.html new file mode 100644 index 0000000000000..658fb2d07dd03 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-generic.html @@ -0,0 +1,42 @@ + + + + HTML-AAM Generic Role Verification Tests + + + + + + + + + + +

    Tests ONLY the generic mappings defined in HTML-AAM. Most test names correspond to a unique ID defined in the spec.

    + +

    These should remain in alphabetical order by tagname.

    + + + +x +x +x +x +
    x
    + + +x +
    x
    +x +x + +x +x +x + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/menu-roles.html b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/menu-roles.html new file mode 100644 index 0000000000000..259c143b9fd82 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/wai-aria/role/menu-roles.html @@ -0,0 +1,63 @@ + + + + Menu-related Role Verification Tests + + + + + + + + + + +

    Tests menu and related roles.

    + + + + + + + + + \ No newline at end of file From 158acabd21e6ba41e596f289c51f4582312af143 Mon Sep 17 00:00:00 2001 From: Nathan van der Kamp Date: Sat, 23 Nov 2024 22:36:37 +0100 Subject: [PATCH 088/237] BindingsGenerator: Add codegen for reflected nullable Element attributes --- .../BindingsGenerator/IDLGenerators.cpp | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 35dd36dcbbfda..356e559a870ca 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -3794,6 +3794,54 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) if (content_attribute_value.has_value()) retval = MUST(Infra::convert_to_scalar_value_string(*content_attribute_value)); )~~~"); + } + // If a reflected IDL attribute has the type T?, where T is either Element + // FIXME: or an interface that inherits from Element, + // then with attr being the reflected content attribute name: + else if (attribute.type->is_nullable() && attribute.type->name() == "Element") { + // The getter steps are to return the result of running this's get the attr-associated element. + attribute_generator.append(R"~~~( + auto retval = GC::Ptr {}; +)~~~"); + + // 1. Let element be the result of running reflectedTarget's get the element. + // 2. Let contentAttributeValue be the result of running reflectedTarget's get the content attribute. + attribute_generator.append(R"~~~( + auto contentAttributeValue = impl->attribute(HTML::AttributeNames::@attribute.reflect_name@); +)~~~"); + // 3. If reflectedTarget's explicitly set attr-element is not null: + // 1. If reflectedTarget's explicitly set attr-element is a descendant of any of element's shadow-including ancestors, then return reflectedTarget's explicitly set attr-element. + // 2. Return null. + attribute_generator.append(R"~~~( + auto const explicitly_set_attr = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->get_@attribute.cpp_name@(); })); + if (explicitly_set_attr) { + if (&impl->shadow_including_root() == &explicitly_set_attr->shadow_including_root()) { + retval = explicitly_set_attr; + } else { + retval = GC::Ptr {}; + } + } +)~~~"); + // 4. Otherwise, if contentAttributeValue is not null, return the first element candidate, in tree order, that meets the following criteria: + // candidate's root is the same as element's root; + // candidate's ID is contentAttributeValue; and + // FIXME: candidate implements T. + // If no such element exists, then return null. + // 5. Return null. + + // FIXME: This works when T is Element but will need adjustment when we handle subtypes too + attribute_generator.append(R"~~~( + else if (contentAttributeValue.has_value()) { + impl->root().for_each_in_inclusive_subtree_of_type([&](auto& candidate) { + if (candidate.attribute(HTML::AttributeNames::id) == contentAttributeValue.value()) { + retval = &candidate; + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + }); + } +)~~~"); + } else { attribute_generator.append(R"~~~( auto retval = impl->get_attribute_value(HTML::AttributeNames::@attribute.reflect_name@); @@ -3894,6 +3942,31 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) } else if (attribute.type->is_integer() && !attribute.type->is_nullable()) { attribute_generator.append(R"~~~( MUST(impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, String::number(cpp_value))); +)~~~"); + } + // If a reflected IDL attribute has the type T?, where T is either Element + // FIXME: or an interface that inherits from Element, + // then with attr being the reflected content attribute name: + else if (attribute.type->is_nullable() && attribute.type->name() == "Element") { + // The setter steps are: + // 1. If the given value is null, then: + // 1. Set this's explicitly set attr-element to null. + // 2. Run this's delete the content attribute. + // 3. Return. + attribute_generator.append(R"~~~( + if (!cpp_value) { + TRY(throw_dom_exception_if_needed(vm, [&] { return impl->set_@attribute.cpp_name@(nullptr); })); + impl->remove_attribute(HTML::AttributeNames::@attribute.reflect_name@); + return JS::js_undefined(); + } +)~~~"); + // 2. Run this's set the content attribute with the empty string. + attribute_generator.append(R"~~~( + MUST(impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, String {})); +)~~~"); + // 3. Set this's explicitly set attr-element to a weak reference to the given value. + attribute_generator.append(R"~~~( + TRY(throw_dom_exception_if_needed(vm, [&] { return impl->set_@attribute.cpp_name@(cpp_value); })); )~~~"); } else if (attribute.type->is_nullable()) { attribute_generator.append(R"~~~( From a276cf2d5e8c54dc76568b69b29b467df41f6b43 Mon Sep 17 00:00:00 2001 From: Nathan van der Kamp Date: Sat, 23 Nov 2024 23:06:06 +0100 Subject: [PATCH 089/237] LibWeb: Add PopOverInvokerElement and use it in HTMLButtonElement The popoverTargetElement seems to be one of the only cases of a reflected Element? attribute in the HTML spec, the behaviour of which is specified in section 2.6.1. Buttons can't actually toggle popovers yet because showing/hiding popovers is not implemented yet. --- Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/HTML/AttributeNames.h | 2 ++ Libraries/LibWeb/HTML/HTMLButtonElement.cpp | 6 ++++ Libraries/LibWeb/HTML/HTMLButtonElement.h | 6 +++- Libraries/LibWeb/HTML/HTMLButtonElement.idl | 3 +- Libraries/LibWeb/HTML/PopoverInvokerElement.h | 34 +++++++++++++++++++ .../LibWeb/HTML/PopoverInvokerElement.idl | 13 +++++++ .../HTML/popover-invoker-attributes.txt | 5 +++ .../HTML/popover-invoker-attributes.html | 29 ++++++++++++++++ 9 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 Libraries/LibWeb/HTML/PopoverInvokerElement.h create mode 100644 Libraries/LibWeb/HTML/PopoverInvokerElement.idl create mode 100644 Tests/LibWeb/Text/expected/HTML/popover-invoker-attributes.txt create mode 100644 Tests/LibWeb/Text/input/HTML/popover-invoker-attributes.html diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index f8b4333471138..966a8a858dc1f 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -513,6 +513,7 @@ class PageTransitionEvent; class Path2D; class Plugin; class PluginArray; +class PopoverInvokerElement; class PromiseRejectionEvent; class RadioNodeList; class SelectedFile; diff --git a/Libraries/LibWeb/HTML/AttributeNames.h b/Libraries/LibWeb/HTML/AttributeNames.h index 439857945b1b0..d82b5ddf479b7 100644 --- a/Libraries/LibWeb/HTML/AttributeNames.h +++ b/Libraries/LibWeb/HTML/AttributeNames.h @@ -238,6 +238,8 @@ namespace AttributeNames { __ENUMERATE_HTML_ATTRIBUTE(placeholder) \ __ENUMERATE_HTML_ATTRIBUTE(playsinline) \ __ENUMERATE_HTML_ATTRIBUTE(popover) \ + __ENUMERATE_HTML_ATTRIBUTE(popovertarget) \ + __ENUMERATE_HTML_ATTRIBUTE(popovertargetaction) \ __ENUMERATE_HTML_ATTRIBUTE(poster) \ __ENUMERATE_HTML_ATTRIBUTE(preload) \ __ENUMERATE_HTML_ATTRIBUTE(readonly) \ diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp index 24ee0d86763c4..8fdfe3e843268 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp @@ -45,6 +45,12 @@ WebIDL::ExceptionOr HTMLButtonElement::set_type(String const& type) return set_attribute(HTML::AttributeNames::type, type); } +void HTMLButtonElement::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + PopoverInvokerElement::visit_edges(visitor); +} + // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex i32 HTMLButtonElement::default_tab_index_value() const { diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Libraries/LibWeb/HTML/HTMLButtonElement.h index a1a834645c1be..b1531848d87ac 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.h +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::HTML { @@ -19,7 +20,8 @@ namespace Web::HTML { class HTMLButtonElement final : public HTMLElement - , public FormAssociatedElement { + , public FormAssociatedElement + , public PopoverInvokerElement { WEB_PLATFORM_OBJECT(HTMLButtonElement, HTMLElement); GC_DECLARE_ALLOCATOR(HTMLButtonElement); FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLButtonElement) @@ -73,6 +75,8 @@ class HTMLButtonElement final virtual void activation_behavior(DOM::Event const&) override; private: + virtual void visit_edges(Visitor&) override; + virtual bool is_html_button_element() const override { return true; } HTMLButtonElement(DOM::Document&, DOM::QualifiedName); diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Libraries/LibWeb/HTML/HTMLButtonElement.idl index 4f836f0a37379..501a1d789818b 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.idl +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.idl @@ -1,5 +1,6 @@ #import #import +#import [MissingValueDefault=submit, InvalidValueDefault=submit] enum ButtonTypeState { @@ -33,4 +34,4 @@ interface HTMLButtonElement : HTMLElement { readonly attribute NodeList labels; }; -// FIXME: HTMLButtonElement includes PopoverInvokerElement; +HTMLButtonElement includes PopoverInvokerElement; diff --git a/Libraries/LibWeb/HTML/PopoverInvokerElement.h b/Libraries/LibWeb/HTML/PopoverInvokerElement.h new file mode 100644 index 0000000000000..0187eaaeeac85 --- /dev/null +++ b/Libraries/LibWeb/HTML/PopoverInvokerElement.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Nathan van der Kamp + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::HTML { + +class PopoverInvokerElement { + +public: + PopoverInvokerElement() { } + + GC::Ptr get_popover_target_element() { return m_popover_target_element; } + + void set_popover_target_element(GC::Ptr value) { m_popover_target_element = value; } + +protected: + void visit_edges(JS::Cell::Visitor& visitor) + { + visitor.visit(m_popover_target_element); + } + +private: + GC::Ptr m_popover_target_element; +}; + +} diff --git a/Libraries/LibWeb/HTML/PopoverInvokerElement.idl b/Libraries/LibWeb/HTML/PopoverInvokerElement.idl new file mode 100644 index 0000000000000..2df03af286489 --- /dev/null +++ b/Libraries/LibWeb/HTML/PopoverInvokerElement.idl @@ -0,0 +1,13 @@ +// https://html.spec.whatwg.org/multipage/popover.html#attr-popovertargetaction +[MissingValueDefault=toggle, InvalidValueDefault=toggle] +enum PopoverTargetActionAttribute { + "toggle", + "show", + "hide" +}; + +// https://html.spec.whatwg.org/multipage/popover.html#popoverinvokerelement +interface mixin PopoverInvokerElement { + [Reflect=popovertarget, CEReactions] attribute Element? popoverTargetElement; + [Reflect=popovertargetaction, Enumerated=PopoverTargetActionAttribute, CEReactions] attribute DOMString popoverTargetAction; +}; diff --git a/Tests/LibWeb/Text/expected/HTML/popover-invoker-attributes.txt b/Tests/LibWeb/Text/expected/HTML/popover-invoker-attributes.txt new file mode 100644 index 0000000000000..137813d0a2b99 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/popover-invoker-attributes.txt @@ -0,0 +1,5 @@ +PASS +PASS +PASS +PASS +PASS diff --git a/Tests/LibWeb/Text/input/HTML/popover-invoker-attributes.html b/Tests/LibWeb/Text/input/HTML/popover-invoker-attributes.html new file mode 100644 index 0000000000000..d3869d9821fc1 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/popover-invoker-attributes.html @@ -0,0 +1,29 @@ + + +
    Popover content
    + + From ccd5b5a7054ab54eb300ef54d12e1b34a2d9d29e Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 12 Dec 2024 14:06:47 +0100 Subject: [PATCH 090/237] CI: Select Xcode 16.1 explicitly instead of `latest-stable` What `latest-stable` means exactly also depends on the image version we're running the workflow on, and unfortunately this can vary wildly between GitHub runners. Fixate the version to 16.1 for now. This version will need to be updated as soon as we want to increase the minimum supported compiler version. --- .github/actions/setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 6688bbe35fc89..46121df240844 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -49,7 +49,7 @@ runs: if: ${{ inputs.os == 'macOS' || inputs.os == 'Android' }} uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: 16.1 - name: 'Install Dependencies' if: ${{ inputs.os == 'macOS' || inputs.os == 'Android' }} From 872a6a11a47b9edfda0c59432f567d274b997fdd Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Mon, 2 Dec 2024 04:09:31 +0100 Subject: [PATCH 091/237] WebCrypto: Unspoof correctness of AES-GSM encryption/decryption --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 62c676d9c6b1a..a476ba7140a20 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -2056,12 +2056,13 @@ WebIDL::ExceptionOr> AesGcm::encrypt(AlgorithmParams co [[maybe_unused]] Bytes tag_span = tag.bytes(); // FIXME: cipher.encrypt(plaintext, ciphertext_span, normalized_algorithm.iv, additional_data, tag_span); + return WebIDL::NotSupportedError::create(m_realm, "AES GCM encryption not yet implemented"_string); // 7. Let ciphertext be equal to C | T, where '|' denotes concatenation. - TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.try_append(tag)); + // TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.try_append(tag)); // 8. Return the result of creating an ArrayBuffer containing ciphertext. - return JS::ArrayBuffer::create(m_realm, ciphertext); + // return JS::ArrayBuffer::create(m_realm, ciphertext); } WebIDL::ExceptionOr> AesGcm::decrypt(AlgorithmParams const& params, GC::Ref key, ByteBuffer const& ciphertext) @@ -2118,16 +2119,16 @@ WebIDL::ExceptionOr> AesGcm::decrypt(AlgorithmParams co [[maybe_unused]] Bytes tag_span = tag.bytes(); // FIXME: auto result = cipher.decrypt(ciphertext, plaintext_span, normalized_algorithm.iv, additional_data, tag_span); - auto result = ::Crypto::VerificationConsistency::Inconsistent; + return WebIDL::NotSupportedError::create(m_realm, "AES GCM decryption not yet implemented"_string); // If the result of the algorithm is the indication of inauthenticity, "FAIL": throw an OperationError - if (result == ::Crypto::VerificationConsistency::Inconsistent) - return WebIDL::OperationError::create(m_realm, "Decryption failed"_string); + // if (result == ::Crypto::VerificationConsistency::Inconsistent) + // return WebIDL::OperationError::create(m_realm, "Decryption failed"_string); // Otherwise: Let plaintext be the output P of the Authenticated Decryption Function. // 9. Return the result of creating an ArrayBuffer containing plaintext. - return JS::ArrayBuffer::create(m_realm, plaintext); + // return JS::ArrayBuffer::create(m_realm, plaintext); } WebIDL::ExceptionOr, GC::Ref>> AesGcm::generate_key(AlgorithmParams const& params, bool extractable, Vector const& key_usages) From fcf6cc27f2cf0c0d0d3c5bae353e348771d5fb98 Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Fri, 6 Dec 2024 23:10:03 +0000 Subject: [PATCH 092/237] LibWeb: Implement popover beforetoggle and toggle events --- Libraries/LibWeb/HTML/HTMLElement.cpp | 77 ++++++- Libraries/LibWeb/HTML/HTMLElement.h | 11 +- .../popover-beforetoggle-opening-event.txt | 6 + .../html/semantics/popovers/togglePopover.txt | 5 +- .../popovers/toggleevent-interface.txt | 42 ++++ .../popover-beforetoggle-opening-event.html | 33 +++ .../popovers/toggleevent-interface.html | 208 ++++++++++++++++++ 7 files changed, 371 insertions(+), 11 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 9428792102fa4..727cf22572069 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1016,9 +1017,21 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except m_popover_showing_or_hiding = false; }; - // FIXME: 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return. + // 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return. + ToggleEventInit event_init {}; + event_init.old_state = "closed"_string; + event_init.new_state = "open"_string; + event_init.cancelable = true; + if (!dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init)))) { + cleanup_showing_flag->function()(); + return {}; + } - // FIXME: 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. + // 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. + if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr))) { + cleanup_showing_flag->function()(); + return {}; + } // 10. Let shouldRestoreFocus be false. bool should_restore_focus = false; @@ -1056,7 +1069,9 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except // FIXME: then set element's previously focused element to originallyFocusedElement. } - // FIXME: 20. Queue a popover toggle event task given element, "closed", and "open". + // 20. Queue a popover toggle event task given element, "closed", and "open". + queue_a_popover_toggle_event_task("closed"_string, "open"_string); + // 21. Run cleanupShowingFlag. cleanup_showing_flag(); @@ -1112,9 +1127,19 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 10. If fireEvents is true: if (fire_events == FireEvents::Yes) { - // FIXME: 10.1. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at element. + // 10.1. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at element. + ToggleEventInit event_init {}; + event_init.old_state = "open"_string; + event_init.new_state = "closed"_string; + dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init))); + // FIXME: 10.2. If autoPopoverListContainsElement is true and document's showing auto popover list's last item is not element, then run hide all popovers until given element, focusPreviousElement, and false. - // FIXME: 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return. + + // 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return. + if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr))) { + cleanup_steps->function()(); + return {}; + } // 10.4. Request an element to be removed from the top layer given element. document.request_an_element_to_be_remove_from_the_top_layer(*this); } else { @@ -1125,7 +1150,9 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 12. Set element's popover visibility state to hidden. m_popover_visibility_state = PopoverVisibilityState::Hidden; - // FIXME: 13. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed". + // 13. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed". + if (fire_events == FireEvents::Yes) + queue_a_popover_toggle_event_task("open"_string, "closed"_string); // FIXME: 14. Let previouslyFocusedElement be element's previously focused element. @@ -1175,6 +1202,44 @@ WebIDL::ExceptionOr HTMLElement::toggle_popover(TogglePopoverOptionsOrForc return popover_visibility_state() == PopoverVisibilityState::Showing; } +// https://html.spec.whatwg.org/multipage/popover.html#queue-a-popover-toggle-event-task +void HTMLElement::queue_a_popover_toggle_event_task(String old_state, String new_state) +{ + // 1. If element's popover toggle task tracker is not null, then: + if (m_popover_toggle_task_tracker.has_value()) { + // 1. Set oldState to element's popover toggle task tracker's old state. + old_state = move(m_popover_toggle_task_tracker->old_state); + + // 2. Remove element's popover toggle task tracker's task from its task queue. + HTML::main_thread_event_loop().task_queue().remove_tasks_matching([&](auto const& task) { + return task.id() == m_popover_toggle_task_tracker->task_id; + }); + + // 3. Set element's popover toggle task tracker to null. + m_popover_toggle_task_tracker->task_id = {}; + } + + // 2. Queue an element task given the DOM manipulation task source and element to run the following steps: + auto task_id = queue_an_element_task(HTML::Task::Source::DOMManipulation, [this, old_state, new_state = move(new_state)]() mutable { + // 1. Fire an event named toggle at element, using ToggleEvent, with the oldState attribute initialized to + // oldState and the newState attribute initialized to newState. + ToggleEventInit event_init {}; + event_init.old_state = move(old_state); + event_init.new_state = move(new_state); + + dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::toggle, move(event_init))); + + // 2. Set element's popover toggle task tracker to null. + m_popover_toggle_task_tracker = {}; + }); + + // 3. Set element's popover toggle task tracker to a struct with task set to the just-queued task and old state set to oldState. + m_popover_toggle_task_tracker = ToggleTaskTracker { + .task_id = task_id, + .old_state = move(old_state), + }; +} + void HTMLElement::did_receive_focus() { if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly)) diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index ac52ac3e53c89..c44bef4327e89 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -6,10 +6,12 @@ #pragma once +#include #include #include #include #include +#include #include namespace Web::HTML { @@ -127,8 +129,6 @@ class HTMLElement WebIDL::ExceptionOr show_popover(ThrowExceptions throw_exceptions, GC::Ptr invoker); WebIDL::ExceptionOr hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions); - WebIDL::ExceptionOr check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr); - protected: HTMLElement(DOM::Document&, DOM::QualifiedName); @@ -155,6 +155,10 @@ class HTMLElement GC::Ptr m_labels; + WebIDL::ExceptionOr check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr); + + void queue_a_popover_toggle_event_task(String old_state, String new_state); + // https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals GC::Ptr m_attached_internals; @@ -174,6 +178,9 @@ class HTMLElement // https://html.spec.whatwg.org/multipage/popover.html#popover-showing-or-hiding bool m_popover_showing_or_hiding { false }; + + // https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute:toggle-task-tracker + Optional m_popover_toggle_task_tracker; }; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt new file mode 100644 index 0000000000000..95da3bfe380c5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Ensure the `beforetoggle` event can be used to populate content before the popover renders \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt index e5c8b8d1c5796..163e59bbeb289 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt @@ -2,8 +2,7 @@ Harness status: OK Found 3 tests -2 Pass -1 Fail +3 Pass Pass togglePopover should toggle the popover and return true or false as specified. -Fail togglePopover's return value should reflect what the end state is, not just the force parameter. +Pass togglePopover's return value should reflect what the end state is, not just the force parameter. Pass togglePopover should throw an exception when there is no popover attribute. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt new file mode 100644 index 0000000000000..c9bae1f73fc6f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt @@ -0,0 +1,42 @@ +Harness status: OK + +Found 37 tests + +37 Pass +Pass the event is an instance of ToggleEvent +Pass the event inherts from Event +Pass Missing type argument +Pass type argument is string +Pass type argument is null +Pass event type set to undefined +Pass oldState has default value of empty string +Pass oldState is readonly +Pass newState has default value of empty string +Pass newState is readonly +Pass ToggleEventInit argument is null +Pass ToggleEventInit argument is undefined +Pass ToggleEventInit argument is empty dictionary +Pass oldState set to 'sample' +Pass oldState set to undefined +Pass oldState set to null +Pass oldState set to false +Pass oldState set to true +Pass oldState set to a number +Pass oldState set to [] +Pass oldState set to [1, 2, 3] +Pass oldState set to an object +Pass oldState set to an object with a valueOf function +Pass ToggleEventInit properties set value +Pass ToggleEventInit properties set value 2 +Pass ToggleEventInit properties set value 3 +Pass ToggleEventInit properties set value 4 +Pass newState set to 'sample' +Pass newState set to undefined +Pass newState set to null +Pass newState set to false +Pass newState set to true +Pass newState set to a number +Pass newState set to [] +Pass newState set to [1, 2, 3] +Pass newState set to an object +Pass newState set to an object with a valueOf function \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html new file mode 100644 index 0000000000000..cd60b6c03ccf2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html @@ -0,0 +1,33 @@ + + +Popover beforetoggle event + + + + + + +
    + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html new file mode 100644 index 0000000000000..0b81a79193d68 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html @@ -0,0 +1,208 @@ + + + + + + + + + From ff7cca38bd2a0d3807948ad859749ba5df4f2865 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sat, 23 Nov 2024 18:28:44 +0400 Subject: [PATCH 093/237] AK: Remove clang-specific templates --- AK/NonnullOwnPtr.h | 19 ------------------- AK/NonnullRefPtr.h | 18 ------------------ 2 files changed, 37 deletions(-) diff --git a/AK/NonnullOwnPtr.h b/AK/NonnullOwnPtr.h index 2fc212bad72b6..a87ce319559a4 100644 --- a/AK/NonnullOwnPtr.h +++ b/AK/NonnullOwnPtr.h @@ -148,15 +148,6 @@ requires(IsConstructible) inline NonnullOwnPtr make(Args&&... arg return NonnullOwnPtr(NonnullOwnPtr::Adopt, *new T(forward(args)...)); } -#ifdef AK_COMPILER_APPLE_CLANG -// FIXME: Remove once P0960R3 is available in Apple Clang. -template -inline NonnullOwnPtr make(Args&&... args) -{ - return NonnullOwnPtr(NonnullOwnPtr::Adopt, *new T { forward(args)... }); -} -#endif - // Use like `adopt_nonnull_own_or_enomem(new (nothrow) T(args...))`. template inline ErrorOr> adopt_nonnull_own_or_enomem(T* object) @@ -172,16 +163,6 @@ requires(IsConstructible) inline ErrorOr> try_make( return adopt_nonnull_own_or_enomem(new (nothrow) T(forward(args)...)); } -#ifdef AK_COMPILER_APPLE_CLANG -// FIXME: Remove once P0960R3 is available in Apple Clang. -template -inline ErrorOr> try_make(Args&&... args) - -{ - return adopt_nonnull_own_or_enomem(new (nothrow) T { forward(args)... }); -} -#endif - template struct Traits> : public DefaultTraits> { using PeekType = T*; diff --git a/AK/NonnullRefPtr.h b/AK/NonnullRefPtr.h index 62a049c134fcb..92df14c462c35 100644 --- a/AK/NonnullRefPtr.h +++ b/AK/NonnullRefPtr.h @@ -240,15 +240,6 @@ requires(IsConstructible) inline ErrorOr> try_make_ return adopt_nonnull_ref_or_enomem(new (nothrow) T(forward(args)...)); } -#ifdef AK_COMPILER_APPLE_CLANG -// FIXME: Remove once P0960R3 is available in Apple Clang. -template -inline ErrorOr> try_make_ref_counted(Args&&... args) -{ - return adopt_nonnull_ref_or_enomem(new (nothrow) T { forward(args)... }); -} -#endif - template struct Formatter> : Formatter { ErrorOr format(FormatBuilder& builder, NonnullRefPtr const& value) @@ -279,15 +270,6 @@ requires(IsConstructible) inline NonnullRefPtr make_ref_counted(A return NonnullRefPtr(NonnullRefPtr::Adopt, *new T(forward(args)...)); } -#ifdef AK_COMPILER_APPLE_CLANG -// FIXME: Remove once P0960R3 is available in Apple Clang. -template -inline NonnullRefPtr make_ref_counted(Args&&... args) -{ - return NonnullRefPtr(NonnullRefPtr::Adopt, *new T { forward(args)... }); -} -#endif - template struct Traits> : public DefaultTraits> { using PeekType = T*; From 24b68259b4675f224892470853afc3dde9ca4fb7 Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Tue, 3 Dec 2024 23:43:23 +0400 Subject: [PATCH 094/237] LibGfx: Remove unnecessary include --- Libraries/LibGfx/PaintStyle.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Libraries/LibGfx/PaintStyle.h b/Libraries/LibGfx/PaintStyle.h index 817fb0204276c..1acbba397b6f9 100644 --- a/Libraries/LibGfx/PaintStyle.h +++ b/Libraries/LibGfx/PaintStyle.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include From 6dfcdaa0d2866e1e6615390fe761391d6e2f192e Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Tue, 3 Dec 2024 23:47:27 +0400 Subject: [PATCH 095/237] LibGfx: Unify bitmap set_pixel() using templates --- Libraries/LibGfx/Bitmap.h | 52 ++++++++++++--------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/Libraries/LibGfx/Bitmap.h b/Libraries/LibGfx/Bitmap.h index 63b55049f7d35..ef92c4bb5c287 100644 --- a/Libraries/LibGfx/Bitmap.h +++ b/Libraries/LibGfx/Bitmap.h @@ -263,40 +263,21 @@ ALWAYS_INLINE Color Bitmap::get_pixel(int x, int y) const } } -template<> -ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) -{ - VERIFY(x >= 0); - VERIFY(x < width()); - scanline(y)[x] = color.value(); -} - -template<> -ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) -{ - VERIFY(x >= 0); - VERIFY(x < width()); - scanline(y)[x] = color.value(); // drop alpha -} - -template<> -ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) +template +ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) { VERIFY(x >= 0); VERIFY(x < width()); - // FIXME: There's a lot of inaccurately named functions in the Color class right now (RGBA vs BGRA), - // clear those up and then make this more convenient. - auto rgba = (color.alpha() << 24) | (color.blue() << 16) | (color.green() << 8) | color.red(); - scanline(y)[x] = rgba; -} -template<> -ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) -{ - VERIFY(x >= 0); - VERIFY(x < width()); - auto rgb = (color.blue() << 16) | (color.green() << 8) | color.red(); - scanline(y)[x] = rgb; + if constexpr (storage_format == StorageFormat::BGRx8888 || storage_format == StorageFormat::BGRA8888) { + scanline(y)[x] = color.value(); + } else if constexpr (storage_format == StorageFormat::RGBA8888) { + scanline(y)[x] = (color.alpha() << 24) | (color.blue() << 16) | (color.green() << 8) | color.red(); + } else if constexpr (storage_format == StorageFormat::RGBx8888) { + scanline(y)[x] = (color.blue() << 16) | (color.green() << 8) | color.red(); + } else { + static_assert(false, "There's a new storage format not in Bitmap::set_pixel"); + } } ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) @@ -304,19 +285,18 @@ ALWAYS_INLINE void Bitmap::set_pixel(int x, int y, Color color) switch (determine_storage_format(m_format)) { case StorageFormat::BGRx8888: set_pixel(x, y, color); - break; + return; case StorageFormat::BGRA8888: set_pixel(x, y, color); - break; + return; case StorageFormat::RGBA8888: set_pixel(x, y, color); - break; + return; case StorageFormat::RGBx8888: set_pixel(x, y, color); - break; - default: - VERIFY_NOT_REACHED(); + return; } + VERIFY_NOT_REACHED(); } } From 7ee3727074abdaadb7dc79c31781aaa748853271 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 12 Dec 2024 15:45:42 -0700 Subject: [PATCH 096/237] LibWeb: Fix compile error for popover element internal lambdas These were converted to lambdas in 6b921e91d4ce4791c67bf467a2ba519b8e3ca88b But I merged fcf6cc27f2cf0c0d0d3c5bae353e348771d5fb98 without checking that the code had responded to the change. --- Libraries/LibWeb/HTML/HTMLElement.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 727cf22572069..4321db9997a3a 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -1023,13 +1023,13 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except event_init.new_state = "open"_string; event_init.cancelable = true; if (!dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init)))) { - cleanup_showing_flag->function()(); + cleanup_showing_flag(); return {}; } // 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr))) { - cleanup_showing_flag->function()(); + cleanup_showing_flag(); return {}; } @@ -1137,7 +1137,7 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return. if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr))) { - cleanup_steps->function()(); + cleanup_steps(); return {}; } // 10.4. Request an element to be removed from the top layer given element. From c1596192fa85328fbc664333aee1ba9ef02ac58e Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 5 Dec 2024 15:46:02 -0700 Subject: [PATCH 097/237] CMake: Add workaround for binutils+patchelf incompatability --- CMakeLists.txt | 4 +++- .../CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ecea1b97769c4..f0ea4b5506884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,9 @@ if (VCPKG_TARGET_ANDROID) include("UI/Android/vcpkg_android.cmake") endif() +# vcpkg flags depend on what linker we are using +include("Meta/CMake/use_linker.cmake") + # Pass additional information to vcpkg toolchain files if we are using vcpkg. if (CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg.cmake$") set(CMAKE_PROJECT_ladybird_INCLUDE_BEFORE "Meta/CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake") @@ -29,7 +32,6 @@ set(LADYBIRD_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") list(APPEND CMAKE_MODULE_PATH "${LADYBIRD_SOURCE_DIR}/Meta/CMake") include(UI/cmake/EnableLagom.cmake) -include(use_linker) include(lagom_options NO_POLICY_SCOPE) include(lagom_compile_options) diff --git a/Meta/CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake b/Meta/CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake index a43a4493d69db..a4c25bb9c565c 100644 --- a/Meta/CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake +++ b/Meta/CMake/vcpkg/generate_vcpkg_toolchain_variables.cmake @@ -8,4 +8,13 @@ if (NOT "${CMAKE_CXX_COMPILER}" STREQUAL "") string(APPEND EXTRA_VCPKG_VARIABLES "set(ENV{CXX} ${CMAKE_CXX_COMPILER})\n") endif() +# Workaround for bad patchelf interaction with binutils 2.43.50 +# https://github.com/LadybirdBrowser/ladybird/issues/2149 +# https://github.com/microsoft/vcpkg/issues/41576 +# https://github.com/NixOS/patchelf/issues/568 +# https://bugzilla.redhat.com/show_bug.cgi?id=2319341 +if (LINUX AND NOT LAGOM_USE_LINKER) + string(APPEND EXTRA_VCPKG_VARIABLES "set(ENV{LDFLAGS} -Wl,-z,noseparate-code)\n") +endif() + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/build-vcpkg-variables.cmake" "${EXTRA_VCPKG_VARIABLES}") From 4dc65c57a0d186db1efba3521e54f3579b505c6f Mon Sep 17 00:00:00 2001 From: Psychpsyo Date: Tue, 19 Nov 2024 22:26:00 +0100 Subject: [PATCH 098/237] LibWeb: Make MouseEvent.offsetX/Y ignore transforms That is what the spec calls it, at least. In code, this manifests as making the offset very aware of the element's transform, because the click position comes relative to the viewport, not to the transformed element. --- Libraries/LibWeb/Page/EventHandler.cpp | 39 +++++++++++++++++-- .../CSSOMView/mouse-event-offset-values.txt | 2 + .../CSSOMView/mouse-event-offset-values.html | 21 ++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/CSSOMView/mouse-event-offset-values.txt create mode 100644 Tests/LibWeb/Text/input/CSSOMView/mouse-event-offset-values.html diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index 961c882327156..9d3e83847ee55 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -147,13 +147,44 @@ static Gfx::StandardCursor cursor_css_to_gfx(Optional cursor) } } +// https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsetx static CSSPixelPoint compute_mouse_event_offset(CSSPixelPoint position, Layout::Node const& layout_node) { - auto top_left_of_layout_node = layout_node.first_paintable()->box_type_agnostic_position(); - return { - position.x() - top_left_of_layout_node.x(), - position.y() - top_left_of_layout_node.y() + // If the event’s dispatch flag is set, + // FIXME: Is this guaranteed to be dispatched? + + // return the x-coordinate of the position where the event occurred, + Gfx::Point precision_offset = { + position.x().to_double(), + position.y().to_double() + }; + + // ignoring the transforms that apply to the element and its ancestors, + if (layout_node.has_css_transform()) { + auto const& paintable_box = layout_node.dom_node()->paintable_box(); + auto const affine_transform = Gfx::extract_2d_affine_transform(paintable_box->transform().inverse()); + + auto const& origin = paintable_box->transform_origin(); + Gfx::Point const precision_origin = { + origin.x().to_double(), + origin.y().to_double() + }; + + precision_offset.translate_by(-precision_origin); + precision_offset.transform_by(affine_transform); + precision_offset.translate_by(precision_origin); + } + + // relative to the origin of the padding edge of the target node + auto const top_left_of_layout_node = layout_node.first_paintable()->box_type_agnostic_position(); + CSSPixelPoint offset = { + CSSPixels(precision_offset.x()), + CSSPixels(precision_offset.y()) }; + offset -= top_left_of_layout_node; + + // and terminate these steps. + return offset; } EventHandler::EventHandler(Badge, HTML::Navigable& navigable) diff --git a/Tests/LibWeb/Text/expected/CSSOMView/mouse-event-offset-values.txt b/Tests/LibWeb/Text/expected/CSSOMView/mouse-event-offset-values.txt new file mode 100644 index 0000000000000..cab2f2a7f70a4 --- /dev/null +++ b/Tests/LibWeb/Text/expected/CSSOMView/mouse-event-offset-values.txt @@ -0,0 +1,2 @@ +offsetX: 45.75 +offsetY: 57.078125 diff --git a/Tests/LibWeb/Text/input/CSSOMView/mouse-event-offset-values.html b/Tests/LibWeb/Text/input/CSSOMView/mouse-event-offset-values.html new file mode 100644 index 0000000000000..39067557c7f46 --- /dev/null +++ b/Tests/LibWeb/Text/input/CSSOMView/mouse-event-offset-values.html @@ -0,0 +1,21 @@ + + +
    + + + + From c199be061a18ad52afc9dd26486e2ace375a618e Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 04:52:05 +0100 Subject: [PATCH 099/237] LibWeb/WebGL: Implement drawingBufferWidth and drawingBufferHeight --- Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp | 12 ++++++++++++ Libraries/LibWeb/WebGL/WebGL2RenderingContext.h | 3 +++ Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp | 12 ++++++++++++ Libraries/LibWeb/WebGL/WebGLRenderingContext.h | 3 +++ Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl | 4 ++-- 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp index 01e647aa56afe..9576416dd84c9 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp @@ -155,4 +155,16 @@ JS::Object* WebGL2RenderingContext::get_extension(String const&) return nullptr; } +WebIDL::Long WebGL2RenderingContext::drawing_buffer_width() const +{ + auto size = canvas_for_binding()->bitmap_size_for_canvas(); + return size.width(); +} + +WebIDL::Long WebGL2RenderingContext::drawing_buffer_height() const +{ + auto size = canvas_for_binding()->bitmap_size_for_canvas(); + return size.height(); +} + } diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h index d60f7ec6dbf93..0740a1820721d 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h @@ -44,6 +44,9 @@ class WebGL2RenderingContext : public Bindings::PlatformObject Optional> get_supported_extensions() const; JS::Object* get_extension(String const& name); + WebIDL::Long drawing_buffer_width() const; + WebIDL::Long drawing_buffer_height() const; + private: virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp index 82a2c2636d4df..b40b9ab74daa4 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp @@ -171,4 +171,16 @@ JS::Object* WebGLRenderingContext::get_extension(String const&) return nullptr; } +WebIDL::Long WebGLRenderingContext::drawing_buffer_width() const +{ + auto size = canvas_for_binding()->bitmap_size_for_canvas(); + return size.width(); +} + +WebIDL::Long WebGLRenderingContext::drawing_buffer_height() const +{ + auto size = canvas_for_binding()->bitmap_size_for_canvas(); + return size.height(); +} + } diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContext.h b/Libraries/LibWeb/WebGL/WebGLRenderingContext.h index 65b652201d776..d0fa4ed82e4cd 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContext.h +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContext.h @@ -43,6 +43,9 @@ class WebGLRenderingContext : public Bindings::PlatformObject Optional> get_supported_extensions() const; JS::Object* get_extension(String const& name); + WebIDL::Long drawing_buffer_width() const; + WebIDL::Long drawing_buffer_height() const; + private: virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl index 599d0c7a2261e..1049344b03b7c 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl @@ -32,8 +32,8 @@ interface mixin WebGLRenderingContextBase { // FIXME: The type should be (HTMLCanvasElement or OffscreenCanvas). [ImplementedAs=canvas_for_binding] readonly attribute HTMLCanvasElement canvas; - [FIXME] readonly attribute GLsizei drawingBufferWidth; - [FIXME] readonly attribute GLsizei drawingBufferHeight; + readonly attribute GLsizei drawingBufferWidth; + readonly attribute GLsizei drawingBufferHeight; [FIXME] readonly attribute GLenum drawingBufferFormat; [FIXME] attribute PredefinedColorSpace drawingBufferColorSpace; From 4e8ec1e793c29a7b22ef37a72d07f98533494de5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 04:58:13 +0100 Subject: [PATCH 100/237] LibWeb/WebGL: Implement readPixels() --- .../WebGL/WebGL2RenderingContextOverloads.idl | 2 +- .../WebGL/WebGLRenderingContextOverloads.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index 2d446c5d10c69..58c506343477d 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -59,7 +59,7 @@ interface mixin WebGL2RenderingContextOverloads { // Reading back pixels // WebGL1: - [FIXME] undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? dstData); + undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); // WebGL2: [FIXME] undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLintptr offset); [FIXME] undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView dstData, unsigned long long dstOffset); diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl index 54d7f075acedf..69c7eaa4afaf8 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl @@ -25,7 +25,7 @@ interface mixin WebGLRenderingContextOverloads { [FIXME] undefined compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, [AllowShared] ArrayBufferView data); [FIXME] undefined compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, [AllowShared] ArrayBufferView data); - [FIXME] undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); + undefined readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLenum format, GLenum type, TexImageSource source); // May throw DOMException diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 6b1fba45104f6..abc0ab90daf5e 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -621,6 +621,28 @@ class @class_name@ { continue; } + if (function.name == "readPixels"sv) { + function_impl_generator.append(R"~~~( + if (!pixels) { + return; + } + + void *ptr = nullptr; + if (pixels->is_data_view()) { + auto& data_view = static_cast(*pixels->raw_object()); + ptr = data_view.viewed_array_buffer()->buffer().data(); + } else if (pixels->is_typed_array_base()) { + auto& typed_array_base = static_cast(*pixels->raw_object()); + ptr = typed_array_base.viewed_array_buffer()->buffer().data(); + } else { + VERIFY_NOT_REACHED(); + } + + glReadPixels(x, y, width, height, format, type, ptr); +)~~~"); + continue; + } + if (function.name == "drawElements"sv) { function_impl_generator.append(R"~~~( glDrawElements(mode, count, type, reinterpret_cast(offset)); From ba19328b9879e8d83c443070a373d8cb89763eec Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 05:18:17 +0100 Subject: [PATCH 101/237] LibWeb/WebGL: Implement vertexAttrib{1,2,3,4}fv --- Libraries/LibWeb/WebGL/Types.idl | 3 +++ .../LibWeb/WebGL/WebGLRenderingContextBase.idl | 8 ++++---- .../WebGL/WebGLRenderingContextOverloads.idl | 3 --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Libraries/LibWeb/WebGL/Types.idl b/Libraries/LibWeb/WebGL/Types.idl index f1d662d63bd8e..f371530e126ce 100644 --- a/Libraries/LibWeb/WebGL/Types.idl +++ b/Libraries/LibWeb/WebGL/Types.idl @@ -13,3 +13,6 @@ typedef unrestricted float GLclampf; // WebGL 2.0 typedef long long GLint64; typedef unsigned long long GLuint64; + +// FIXME: BufferSource should be a Float32Array +typedef (BufferSource or sequence) Float32List; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl index 1049344b03b7c..ec8ac45c8a95b 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.idl @@ -182,10 +182,10 @@ interface mixin WebGLRenderingContextBase { undefined vertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z); undefined vertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); - [FIXME] undefined vertexAttrib1fv(GLuint index, Float32List values); - [FIXME] undefined vertexAttrib2fv(GLuint index, Float32List values); - [FIXME] undefined vertexAttrib3fv(GLuint index, Float32List values); - [FIXME] undefined vertexAttrib4fv(GLuint index, Float32List values); + undefined vertexAttrib1fv(GLuint index, Float32List values); + undefined vertexAttrib2fv(GLuint index, Float32List values); + undefined vertexAttrib3fv(GLuint index, Float32List values); + undefined vertexAttrib4fv(GLuint index, Float32List values); undefined vertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl index 69c7eaa4afaf8..fb07c3dc8d606 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.idl @@ -9,9 +9,6 @@ typedef (ImageBitmap or // FIXME: VideoFrame ) TexImageSource; -// FIXME: BufferSource should be a Float32Array -typedef (BufferSource or sequence) Float32List; - // FIXME: BufferSource should be a Int32Array typedef (BufferSource or sequence) Int32List; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index abc0ab90daf5e..fdcb840227c5a 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -709,6 +709,24 @@ class @class_name@ { continue; } + if (function.name == "vertexAttrib1fv"sv || function.name == "vertexAttrib2fv"sv || function.name == "vertexAttrib3fv"sv || function.name == "vertexAttrib4fv"sv) { + auto number_of_vector_elements = function.name.substring_view(12, 1); + function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); + function_impl_generator.append(R"~~~( + if (values.has>()) { + auto& data = values.get>(); + glVertexAttrib@number_of_vector_elements@fv(index, data.data()); + return; + } + + auto& typed_array_base = static_cast(*values.get>()->raw_object()); + auto& float32_array = verify_cast(typed_array_base); + float const* data = float32_array.data().data(); + glVertexAttrib@number_of_vector_elements@fv(index, data); +)~~~"); + continue; + } + if (function.name == "getParameter"sv) { generate_get_parameter(function_impl_generator); continue; From af4f0c5a813a08798e279f52045e4ab2caa04c0e Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 05:30:03 +0100 Subject: [PATCH 102/237] LibWeb/WebGL: Implement texImage3D() --- .../WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index be1b583442c33..2d934e4c0a1f2 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -314,7 +314,7 @@ interface mixin WebGL2RenderingContextBase { // May throw DOMException [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, TexImageSource source); - [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData); + undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData); [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); [FIXME] undefined texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLintptr pboOffset); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index fdcb840227c5a..182be28937acd 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -540,6 +540,25 @@ class @class_name@ { continue; } + if (function.name == "texImage3D"sv) { + // FIXME: If a WebGLBuffer is bound to the PIXEL_UNPACK_BUFFER target, generates an INVALID_OPERATION error. + // FIXME: If srcData is null, a buffer of sufficient size initialized to 0 is passed. + // FIXME: If type is specified as FLOAT_32_UNSIGNED_INT_24_8_REV, srcData must be null; otherwise, generates an INVALID_OPERATION error. + // FIXME: If srcData is non-null, the type of srcData must match the type according to the above table; otherwise, generate an INVALID_OPERATION error. + // FIXME: If an attempt is made to call this function with no WebGLTexture bound (see above), generates an INVALID_OPERATION error. + // FIXME: If there's not enough data in srcData starting at srcOffset, generate INVALID_OPERATION. + function_impl_generator.append(R"~~~( + void const* src_data_ptr = nullptr; + if (src_data) { + auto const& viewed_array_buffer = src_data->viewed_array_buffer(); + auto const& byte_buffer = viewed_array_buffer->buffer(); + src_data_ptr = byte_buffer.data(); + } + glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, src_data_ptr); +)~~~"); + continue; + } + if (function.name == "texImage2D"sv && function.overload_index == 1) { // FIXME: If this function is called with an ImageData whose data attribute has been neutered, // an INVALID_VALUE error is generated. From 145bb0f84923f12716afdb14dbc87d7f6f04f4fb Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 05:49:33 +0100 Subject: [PATCH 103/237] LibWeb/WebGL: Implement getSupportedExtensions() --- Libraries/LibWeb/WebGL/OpenGLContext.cpp | 82 +++++++++++++++++++ Libraries/LibWeb/WebGL/OpenGLContext.h | 2 + .../LibWeb/WebGL/WebGL2RenderingContext.cpp | 4 +- .../LibWeb/WebGL/WebGL2RenderingContext.h | 2 +- .../LibWeb/WebGL/WebGLRenderingContext.cpp | 4 +- .../LibWeb/WebGL/WebGLRenderingContext.h | 2 +- 6 files changed, 90 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/WebGL/OpenGLContext.cpp b/Libraries/LibWeb/WebGL/OpenGLContext.cpp index 95a6504c1126e..2067d901a1b15 100644 --- a/Libraries/LibWeb/WebGL/OpenGLContext.cpp +++ b/Libraries/LibWeb/WebGL/OpenGLContext.cpp @@ -204,4 +204,86 @@ RefPtr OpenGLContext::surface() return m_painting_surface; } +Vector s_available_webgl_extensions { + // Khronos ratified WebGL Extensions + "ANGLE_instanced_arrays"sv, + "EXT_blend_minmax"sv, + "EXT_frag_depth"sv, + "EXT_shader_texture_lod"sv, + "EXT_texture_filter_anisotropic"sv, + "OES_element_index_uint"sv, + "OES_standard_derivatives"sv, + "OES_texture_float"sv, + "OES_texture_float_linear"sv, + "OES_texture_half_float"sv, + "OES_texture_half_float_linear"sv, + "OES_vertex_array_object"sv, + "WEBGL_compressed_texture_s3tc"sv, + "WEBGL_debug_renderer_info"sv, + "WEBGL_debug_shaders"sv, + "WEBGL_depth_texture"sv, + "WEBGL_draw_buffers"sv, + "WEBGL_lose_context"sv, + + // Community approved WebGL Extensions + "EXT_clip_control"sv, + "EXT_color_buffer_float"sv, + "EXT_color_buffer_half_float"sv, + "EXT_conservative_depth"sv, + "EXT_depth_clamp"sv, + "EXT_disjoint_timer_query"sv, + "EXT_disjoint_timer_query_webgl2"sv, + "EXT_float_blend"sv, + "EXT_polygon_offset_clamp"sv, + "EXT_render_snorm"sv, + "EXT_sRGB"sv, + "EXT_texture_compression_bptc"sv, + "EXT_texture_compression_rgtc"sv, + "EXT_texture_mirror_clamp_to_edge"sv, + "EXT_texture_norm16"sv, + "KHR_parallel_shader_compile"sv, + "NV_shader_noperspective_interpolation"sv, + "OES_draw_buffers_indexed"sv, + "OES_fbo_render_mipmap"sv, + "OES_sample_variables"sv, + "OES_shader_multisample_interpolation"sv, + "OVR_multiview2"sv, + "WEBGL_blend_func_extended"sv, + "WEBGL_clip_cull_distance"sv, + "WEBGL_color_buffer_float"sv, + "WEBGL_compressed_texture_astc"sv, + "WEBGL_compressed_texture_etc"sv, + "WEBGL_compressed_texture_etc1"sv, + "WEBGL_compressed_texture_pvrtc"sv, + "WEBGL_compressed_texture_s3tc_srgb"sv, + "WEBGL_multi_draw"sv, + "WEBGL_polygon_mode"sv, + "WEBGL_provoking_vertex"sv, + "WEBGL_render_shared_exponent"sv, + "WEBGL_stencil_texturing"sv, +}; + +Vector OpenGLContext::get_supported_extensions() +{ +#ifdef AK_OS_MACOS + make_current(); + + auto const* extensions_string = reinterpret_cast(glGetString(GL_EXTENSIONS)); + StringView extensions_view(extensions_string, strlen(extensions_string)); + + Vector extensions; + for (auto const& extension : extensions_view.split_view(' ')) { + auto extension_name_without_gl_prefix = extension.substring_view(3); + // FIXME: WebGL 1 and WebGL 2 have different sets of available extensions, but for now we simply + // filter out everything that is not listed in https://registry.khronos.org/webgl/extensions/ + if (s_available_webgl_extensions.contains_slow(extension_name_without_gl_prefix)) + extensions.append(MUST(String::from_utf8(extension_name_without_gl_prefix))); + } + + return extensions; +#else + return {}; +#endif +} + } diff --git a/Libraries/LibWeb/WebGL/OpenGLContext.h b/Libraries/LibWeb/WebGL/OpenGLContext.h index d9c15601d8937..f76c25da6af1b 100644 --- a/Libraries/LibWeb/WebGL/OpenGLContext.h +++ b/Libraries/LibWeb/WebGL/OpenGLContext.h @@ -30,6 +30,8 @@ class OpenGLContext { RefPtr surface(); + Vector get_supported_extensions(); + private: NonnullRefPtr m_skia_backend_context; Gfx::IntSize m_size; diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp index 9576416dd84c9..32a82000097fe 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.cpp @@ -145,9 +145,9 @@ void WebGL2RenderingContext::allocate_painting_surface_if_needed() context().allocate_painting_surface_if_needed(); } -Optional> WebGL2RenderingContext::get_supported_extensions() const +Optional> WebGL2RenderingContext::get_supported_extensions() { - return {}; + return context().get_supported_extensions(); } JS::Object* WebGL2RenderingContext::get_extension(String const&) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h index 0740a1820721d..5eb4dd64c7db6 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContext.h @@ -41,7 +41,7 @@ class WebGL2RenderingContext : public Bindings::PlatformObject void set_size(Gfx::IntSize const&); void reset_to_default_state(); - Optional> get_supported_extensions() const; + Optional> get_supported_extensions(); JS::Object* get_extension(String const& name); WebIDL::Long drawing_buffer_width() const; diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp index b40b9ab74daa4..42245ddf0edc0 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContext.cpp @@ -161,9 +161,9 @@ void WebGLRenderingContext::allocate_painting_surface_if_needed() context().allocate_painting_surface_if_needed(); } -Optional> WebGLRenderingContext::get_supported_extensions() const +Optional> WebGLRenderingContext::get_supported_extensions() { - return {}; + return context().get_supported_extensions(); } JS::Object* WebGLRenderingContext::get_extension(String const&) diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContext.h b/Libraries/LibWeb/WebGL/WebGLRenderingContext.h index d0fa4ed82e4cd..55f0abc4fb847 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContext.h +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContext.h @@ -40,7 +40,7 @@ class WebGLRenderingContext : public Bindings::PlatformObject void set_size(Gfx::IntSize const&); void reset_to_default_state(); - Optional> get_supported_extensions() const; + Optional> get_supported_extensions(); JS::Object* get_extension(String const& name); WebIDL::Long drawing_buffer_width() const; From 73479d3f94ef1575ce378c181cc30c19b66f7547 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 05:54:57 +0100 Subject: [PATCH 104/237] LibWeb/WebGL: Implement drawBuffers() --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 2d934e4c0a1f2..52389fc2367b9 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -368,7 +368,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset); // Multiple Render Targets - [FIXME] undefined drawBuffers(sequence buffers); + undefined drawBuffers(sequence buffers); [FIXME] undefined clearBufferfv(GLenum buffer, GLint drawbuffer, Float32List values, optional unsigned long long srcOffset = 0); [FIXME] undefined clearBufferiv(GLenum buffer, GLint drawbuffer, Int32List values, optional unsigned long long srcOffset = 0); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 182be28937acd..b453babd8bfb4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -670,6 +670,13 @@ class @class_name@ { continue; } + if (function.name == "drawBuffers"sv) { + function_impl_generator.append(R"~~~( + glDrawBuffers(buffers.size(), buffers.data()); +)~~~"); + continue; + } + if (function.name.starts_with("uniformMatrix"sv)) { auto number_of_matrix_elements = function.name.substring_view(13, 1); function_impl_generator.set("number_of_matrix_elements", number_of_matrix_elements); From 544a0216cf169d1e2b62fca661143a8d1993e5d9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 06:36:44 +0100 Subject: [PATCH 105/237] CodeGenerators: Change variable name from "matrix" to "vector" [WebGL] --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index b453babd8bfb4..b32579d9a9c09 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -698,39 +698,39 @@ class @class_name@ { } if (function.name == "uniform1fv"sv || function.name == "uniform2fv"sv || function.name == "uniform3fv"sv || function.name == "uniform4fv"sv) { - auto number_of_matrix_elements = function.name.substring_view(7, 1); - function_impl_generator.set("number_of_matrix_elements", number_of_matrix_elements); + auto number_of_vector_elements = function.name.substring_view(7, 1); + function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( if (v.has>()) { auto& data = v.get>(); - glUniform@number_of_matrix_elements@fv(location->handle(), data.size() / @number_of_matrix_elements@, data.data()); + glUniform@number_of_vector_elements@fv(location->handle(), data.size() / @number_of_vector_elements@, data.data()); return; } auto& typed_array_base = static_cast(*v.get>()->raw_object()); auto& float32_array = verify_cast(typed_array_base); float const* data = float32_array.data().data(); - auto count = float32_array.array_length().length() / @number_of_matrix_elements@; - glUniform@number_of_matrix_elements@fv(location->handle(), count, data); + auto count = float32_array.array_length().length() / @number_of_vector_elements@; + glUniform@number_of_vector_elements@fv(location->handle(), count, data); )~~~"); continue; } if (function.name == "uniform1iv"sv || function.name == "uniform2iv"sv || function.name == "uniform3iv"sv || function.name == "uniform4iv"sv) { - auto number_of_matrix_elements = function.name.substring_view(7, 1); - function_impl_generator.set("number_of_matrix_elements", number_of_matrix_elements); + auto number_of_vector_elements = function.name.substring_view(7, 1); + function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( if (v.has>()) { auto& data = v.get>(); - glUniform@number_of_matrix_elements@iv(location->handle(), data.size() / @number_of_matrix_elements@, data.data()); + glUniform@number_of_vector_elements@iv(location->handle(), data.size() / @number_of_vector_elements@, data.data()); return; } auto& typed_array_base = static_cast(*v.get>()->raw_object()); auto& int32_array = verify_cast(typed_array_base); int const* data = int32_array.data().data(); - auto count = int32_array.array_length().length() / @number_of_matrix_elements@; - glUniform@number_of_matrix_elements@iv(location->handle(), count, data); + auto count = int32_array.array_length().length() / @number_of_vector_elements@; + glUniform@number_of_vector_elements@iv(location->handle(), count, data); )~~~"); continue; } From 194edbfd89426ae7c5bbd107d9d4bcc2eaa36266 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 07:44:27 +0100 Subject: [PATCH 106/237] CodeGenerators: Unify generation of uniform{1,2,3,4}{i,f}v [WebGL] --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index b32579d9a9c09..de31b3329609d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -697,40 +697,33 @@ class @class_name@ { continue; } - if (function.name == "uniform1fv"sv || function.name == "uniform2fv"sv || function.name == "uniform3fv"sv || function.name == "uniform4fv"sv) { - auto number_of_vector_elements = function.name.substring_view(7, 1); - function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); - function_impl_generator.append(R"~~~( - if (v.has>()) { - auto& data = v.get>(); - glUniform@number_of_vector_elements@fv(location->handle(), data.size() / @number_of_vector_elements@, data.data()); - return; - } - - auto& typed_array_base = static_cast(*v.get>()->raw_object()); - auto& float32_array = verify_cast(typed_array_base); - float const* data = float32_array.data().data(); - auto count = float32_array.array_length().length() / @number_of_vector_elements@; - glUniform@number_of_vector_elements@fv(location->handle(), count, data); -)~~~"); - continue; - } - - if (function.name == "uniform1iv"sv || function.name == "uniform2iv"sv || function.name == "uniform3iv"sv || function.name == "uniform4iv"sv) { + if (function.name == "uniform1fv"sv || function.name == "uniform2fv"sv || function.name == "uniform3fv"sv || function.name == "uniform4fv"sv || function.name == "uniform1iv"sv || function.name == "uniform2iv"sv || function.name == "uniform3iv"sv || function.name == "uniform4iv"sv) { auto number_of_vector_elements = function.name.substring_view(7, 1); + auto element_type = function.name.substring_view(8, 1); + if (element_type == "f"sv) { + function_impl_generator.set("cpp_element_type", "float"sv); + function_impl_generator.set("typed_array_type", "Float32Array"sv); + function_impl_generator.set("gl_postfix", "f"sv); + } else if (element_type == "i"sv) { + function_impl_generator.set("cpp_element_type", "int"sv); + function_impl_generator.set("typed_array_type", "Int32Array"sv); + function_impl_generator.set("gl_postfix", "i"sv); + } else { + VERIFY_NOT_REACHED(); + } function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( - if (v.has>()) { - auto& data = v.get>(); - glUniform@number_of_vector_elements@iv(location->handle(), data.size() / @number_of_vector_elements@, data.data()); + if (v.has>()) { + auto& data = v.get>(); + glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), data.size() / @number_of_vector_elements@, data.data()); return; } auto& typed_array_base = static_cast(*v.get>()->raw_object()); - auto& int32_array = verify_cast(typed_array_base); - int const* data = int32_array.data().data(); - auto count = int32_array.array_length().length() / @number_of_vector_elements@; - glUniform@number_of_vector_elements@iv(location->handle(), count, data); + auto& typed_array = verify_cast(typed_array_base); + @cpp_element_type@ const* data = typed_array.data().data(); + auto count = typed_array.array_length().length() / @number_of_vector_elements@; + glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), count, data); )~~~"); continue; } From cfff38a176fa3c1ead39dffe5ba737445bd01e8f Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 09:36:54 +0100 Subject: [PATCH 107/237] LibWeb/WebGL: Implement uniform{1,2,3,4}{i,f}v calls for WebGL2 --- .../WebGL/WebGL2RenderingContextOverloads.idl | 18 ++++----- .../LibWeb/GenerateWebGLRenderingContext.cpp | 37 +++++++++++++++---- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index 58c506343477d..3ed7b1b6b0faf 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -43,15 +43,15 @@ interface mixin WebGL2RenderingContextOverloads { [FIXME] undefined compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, GLintptr offset); [FIXME] undefined compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,GLsizei width, GLsizei height, GLenum format, [AllowShared] ArrayBufferView srcData, optional unsigned long long srcOffset = 0, optional GLuint srcLengthOverride = 0); - [FIXME] undefined uniform1fv(WebGLUniformLocation? location, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform2fv(WebGLUniformLocation? location, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform3fv(WebGLUniformLocation? location, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform4fv(WebGLUniformLocation? location, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - - [FIXME] undefined uniform1iv(WebGLUniformLocation? location, Int32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform2iv(WebGLUniformLocation? location, Int32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform3iv(WebGLUniformLocation? location, Int32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniform4iv(WebGLUniformLocation? location, Int32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform1fv(WebGLUniformLocation? location, Float32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform2fv(WebGLUniformLocation? location, Float32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform3fv(WebGLUniformLocation? location, Float32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform4fv(WebGLUniformLocation? location, Float32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + + undefined uniform1iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform2iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform3iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniform4iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); [FIXME] undefined uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); [FIXME] undefined uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index de31b3329609d..d875c74358385 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -326,7 +326,7 @@ ErrorOr serenity_main(Main::Arguments arguments) SourceGenerator implementation_file_generator { implementation_file_string_builder }; implementation_file_generator.set("class_name", class_name); - auto webgl_version = class_name == "WebGLRenderingContext" ? 1 : 2; + auto webgl_version = class_name == "WebGLRenderingContextImpl" ? 1 : 2; if (webgl_version == 1) { implementation_file_generator.append(R"~~~( #include @@ -713,17 +713,38 @@ class @class_name@ { } function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( + @cpp_element_type@ const* data = nullptr; + size_t count = 0; if (v.has>()) { - auto& data = v.get>(); - glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), data.size() / @number_of_vector_elements@, data.data()); + auto& vector = v.get>(); + data = vector.data(); + count = vector.size(); + } else if (v.has>()) { + auto& typed_array_base = static_cast(*v.get>()->raw_object()); + auto& typed_array = verify_cast(typed_array_base); + data = typed_array.data().data(); + count = typed_array.array_length().length(); + } else { + VERIFY_NOT_REACHED(); + } +)~~~"); + + if (webgl_version == 2) { + function_impl_generator.append(R"~~~( + data += src_offset; + if (src_length == 0) { + count -= src_offset; + } + + if (src_offset + src_length <= count) { + set_error(GL_INVALID_VALUE); return; } +)~~~"); + } - auto& typed_array_base = static_cast(*v.get>()->raw_object()); - auto& typed_array = verify_cast(typed_array_base); - @cpp_element_type@ const* data = typed_array.data().data(); - auto count = typed_array.array_length().length() / @number_of_vector_elements@; - glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), count, data); + function_impl_generator.append(R"~~~( + glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), count / @number_of_vector_elements@, data); )~~~"); continue; } From 897883f9470978819e89ab55bfa182a4e6280156 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 10 Dec 2024 10:29:32 +0100 Subject: [PATCH 108/237] LibWeb/WebGL: Implement texStorage2D() --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 52389fc2367b9..6c46beb1c61b8 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -306,7 +306,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); // Texture objects - [FIXME] undefined texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); + undefined texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); [FIXME] undefined texStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, GLintptr pboOffset); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index d875c74358385..0b30b804454ad 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -527,6 +527,13 @@ class @class_name@ { continue; } + if (function.name == "texStorage2D") { + function_impl_generator.append(R"~~~( + glTexStorage2D(target, levels, internalformat, width, height); +)~~~"); + continue; + } + if (function.name == "texImage2D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; From f8092455e200f1d1ff8a452efd23bdddfd00ce0e Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Sun, 17 Nov 2024 23:34:18 +0100 Subject: [PATCH 109/237] LibRegex: Print OpCode_Repeat's offset as ssize_t --- Libraries/LibRegex/RegexByteCode.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Libraries/LibRegex/RegexByteCode.h b/Libraries/LibRegex/RegexByteCode.h index 34a1da3b88991..fa8eff1a03629 100644 --- a/Libraries/LibRegex/RegexByteCode.h +++ b/Libraries/LibRegex/RegexByteCode.h @@ -787,7 +787,13 @@ class OpCode_Repeat : public OpCode { ByteString arguments_string() const override { auto reps = id() < state().repetition_marks.size() ? state().repetition_marks.at(id()) : 0; - return ByteString::formatted("offset={} count={} id={} rep={}, sp: {}", offset(), count() + 1, id(), reps + 1, state().string_position); + return ByteString::formatted("offset={} [&{}] count={} id={} rep={}, sp: {}", + static_cast(offset()), + state().instruction_position - offset(), + count() + 1, + id(), + reps + 1, + state().string_position); } }; From 44798f44ef1c112aa9dbee7abee7ff6a5568c4f4 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 10 Dec 2024 23:33:38 +0100 Subject: [PATCH 110/237] AK: Add Vector::get(index) convenience function This is similar to HashMap::get(key), which returns an Optional, empty if the index is out of bounds for the vector. --- AK/Vector.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/AK/Vector.h b/AK/Vector.h index 41ac977fa1399..16d83d6c4fa6f 100644 --- a/AK/Vector.h +++ b/AK/Vector.h @@ -155,6 +155,20 @@ requires(!IsRvalueReference) class Vector { ALWAYS_INLINE VisibleType const& operator[](size_t i) const { return at(i); } ALWAYS_INLINE VisibleType& operator[](size_t i) { return at(i); } + Optional get(size_t i) + { + if (i >= size()) + return {}; + return at(i); + } + + Optional get(size_t i) const + { + if (i >= size()) + return {}; + return at(i); + } + VisibleType const& first() const { return at(0); } VisibleType& first() { return at(0); } From 4a8d3e35a37cf94b6d076eec7b242d6924b17757 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Wed, 11 Dec 2024 10:55:38 +0100 Subject: [PATCH 111/237] LibRegex: Add some more debugging info to bytecode block ranges These were getting difficult to differentiate, now they each get a comment on where they came from to aid with future debugging. --- Libraries/LibRegex/RegexMatcher.h | 1 + Libraries/LibRegex/RegexOptimizer.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Libraries/LibRegex/RegexMatcher.h b/Libraries/LibRegex/RegexMatcher.h index 1cbe13058b37e..7dd3509d5adc2 100644 --- a/Libraries/LibRegex/RegexMatcher.h +++ b/Libraries/LibRegex/RegexMatcher.h @@ -25,6 +25,7 @@ namespace Detail { struct Block { size_t start; size_t end; + StringView comment { "N/A"sv }; }; } diff --git a/Libraries/LibRegex/RegexOptimizer.cpp b/Libraries/LibRegex/RegexOptimizer.cpp index df8b1ac0b2c1a..821aa23424161 100644 --- a/Libraries/LibRegex/RegexOptimizer.cpp +++ b/Libraries/LibRegex/RegexOptimizer.cpp @@ -59,18 +59,18 @@ typename Regex::BasicBlockList Regex::split_basic_blocks(ByteCod auto& op = static_cast(opcode); ssize_t jump_offset = op.size() + op.offset(); if (jump_offset >= 0) { - block_boundaries.append({ end_of_last_block, state.instruction_position }); + block_boundaries.append({ end_of_last_block, state.instruction_position, "Jump ahead"sv }); end_of_last_block = state.instruction_position + opcode.size(); } else { // This op jumps back, see if that's within this "block". if (jump_offset + state.instruction_position > end_of_last_block) { // Split the block! - block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position }); - block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position }); + block_boundaries.append({ end_of_last_block, jump_offset + state.instruction_position, "Jump back 1"sv }); + block_boundaries.append({ jump_offset + state.instruction_position, state.instruction_position, "Jump back 2"sv }); end_of_last_block = state.instruction_position + opcode.size(); } else { // Nope, it's just a jump to another block - block_boundaries.append({ end_of_last_block, state.instruction_position }); + block_boundaries.append({ end_of_last_block, state.instruction_position, "Jump"sv }); end_of_last_block = state.instruction_position + opcode.size(); } } @@ -92,15 +92,15 @@ typename Regex::BasicBlockList Regex::split_basic_blocks(ByteCod check_jump.template operator()(opcode); break; case OpCodeId::FailForks: - block_boundaries.append({ end_of_last_block, state.instruction_position }); + block_boundaries.append({ end_of_last_block, state.instruction_position, "FailForks"sv }); end_of_last_block = state.instruction_position + opcode.size(); break; case OpCodeId::Repeat: { // Repeat produces two blocks, one containing its repeated expr, and one after that. auto repeat_start = state.instruction_position - static_cast(opcode).offset(); if (repeat_start > end_of_last_block) - block_boundaries.append({ end_of_last_block, repeat_start }); - block_boundaries.append({ repeat_start, state.instruction_position }); + block_boundaries.append({ end_of_last_block, repeat_start, "Repeat"sv }); + block_boundaries.append({ repeat_start, state.instruction_position, "Repeat after"sv }); end_of_last_block = state.instruction_position + opcode.size(); break; } @@ -116,7 +116,7 @@ typename Regex::BasicBlockList Regex::split_basic_blocks(ByteCod } if (end_of_last_block < bytecode_size) - block_boundaries.append({ end_of_last_block, bytecode_size }); + block_boundaries.append({ end_of_last_block, bytecode_size, "End"sv }); quick_sort(block_boundaries, [](auto& a, auto& b) { return a.start < b.start; }); @@ -664,7 +664,7 @@ void Regex::attempt_rewrite_loops_as_atomic_groups(BasicBlockList const& RegexDebug dbg; dbg.print_bytecode(*this); for (auto const& block : basic_blocks) - dbgln("block from {} to {}", block.start, block.end); + dbgln("block from {} to {} (comment: {})", block.start, block.end, block.comment); } // A pattern such as: From 358378c1c073440ef830a3485e7d6cb9382e0c92 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Wed, 11 Dec 2024 10:56:32 +0100 Subject: [PATCH 112/237] LibRegex: Pick the right target for OpCode_Repeat Repeat's 'offset' field is a bit odd in that it is treated as a negative offset, causing a backwards jump when positive; the optimizer didn't correctly model this behaviour, which caused crashes and misopts when dealing with Repeats. This commit fixes that behaviour. --- Libraries/LibRegex/RegexOptimizer.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/LibRegex/RegexOptimizer.cpp b/Libraries/LibRegex/RegexOptimizer.cpp index 821aa23424161..b3d08d935c23d 100644 --- a/Libraries/LibRegex/RegexOptimizer.cpp +++ b/Libraries/LibRegex/RegexOptimizer.cpp @@ -97,7 +97,8 @@ typename Regex::BasicBlockList Regex::split_basic_blocks(ByteCod break; case OpCodeId::Repeat: { // Repeat produces two blocks, one containing its repeated expr, and one after that. - auto repeat_start = state.instruction_position - static_cast(opcode).offset(); + auto& repeat = static_cast(opcode); + auto repeat_start = state.instruction_position - repeat.offset() - repeat.size(); if (repeat_start > end_of_last_block) block_boundaries.append({ end_of_last_block, repeat_start, "Repeat"sv }); block_boundaries.append({ repeat_start, state.instruction_position, "Repeat after"sv }); @@ -1221,6 +1222,7 @@ void Optimizer::append_alternation(ByteCode& target, Span alternatives ssize_t jump_offset; auto is_jump = true; auto patch_location = state.instruction_position + 1; + bool should_negate = false; switch (opcode.opcode_id()) { case OpCodeId::Jump: @@ -1243,6 +1245,7 @@ void Optimizer::append_alternation(ByteCode& target, Span alternatives break; case OpCodeId::Repeat: jump_offset = static_cast(0) - static_cast(static_cast(opcode).offset()) - static_cast(opcode.size()); + should_negate = true; break; default: is_jump = false; @@ -1272,7 +1275,10 @@ void Optimizer::append_alternation(ByteCode& target, Span alternatives intended_jump_ip); VERIFY_NOT_REACHED(); } - target[patch_location] = static_cast(*target_ip - patch_location - 1); + ssize_t target_value = *target_ip - patch_location - 1; + if (should_negate) + target_value = -target_value + 2; // from -1 to +1. + target[patch_location] = static_cast(target_value); } else { patch_locations.append({ QualifiedIP { ip.alternative_index, intended_jump_ip }, patch_location }); } From eee90f4aa23afd66ce6007faf4943d4ab56d4f22 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 10 Dec 2024 23:34:29 +0100 Subject: [PATCH 113/237] LibRegex: Treat checks against nonexistent checkpoints as empty Due to optimiser shenanigans in the tree alternative form, some JumpNonEmpty ops might be moved before their Checkpoint instruction. It is safe to assume the distance between the nonexistent checkpoint and the current op is zero, so just do that. --- Libraries/LibRegex/RegexByteCode.cpp | 2 +- Tests/LibRegex/Regex.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/LibRegex/RegexByteCode.cpp b/Libraries/LibRegex/RegexByteCode.cpp index 5afe9d8ca1b07..7ff0b4a6dfb8a 100644 --- a/Libraries/LibRegex/RegexByteCode.cpp +++ b/Libraries/LibRegex/RegexByteCode.cpp @@ -1085,7 +1085,7 @@ ALWAYS_INLINE ExecutionResult OpCode_Checkpoint::execute(MatchInput const&, Matc ALWAYS_INLINE ExecutionResult OpCode_JumpNonEmpty::execute(MatchInput const& input, MatchState& state) const { u64 current_position = state.string_position; - auto checkpoint_position = state.checkpoints[checkpoint()]; + auto checkpoint_position = state.checkpoints.get(checkpoint()).value_or(0); if (checkpoint_position != 0 && checkpoint_position != current_position + 1) { auto form = this->form(); diff --git a/Tests/LibRegex/Regex.cpp b/Tests/LibRegex/Regex.cpp index 0f11865fc64d2..d32b5e820f18b 100644 --- a/Tests/LibRegex/Regex.cpp +++ b/Tests/LibRegex/Regex.cpp @@ -1087,6 +1087,8 @@ TEST_CASE(optimizer_alternation) Tuple { "[0-9]{2}|[0-9]"sv, "92"sv, 2u }, // Don't ForkJump to the next instruction, rerunning it would produce the same result. see ladybird#2398. Tuple { "(xxxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxx)?b"sv, "xxxxxxxxxxxxxxxxxxxxxxx"sv, 0u }, + // Don't take the jump in JumpNonEmpty with nonexistent checkpoints (also don't crash). + Tuple { "(?!\\d*|[g-ta-r]+|[h-l]|\\S|\\S|\\S){,9}|\\S{7,8}|\\d|(?)|[c-mj-tb-o]*|\\s"sv, "rjvogg7pm|li4nmct mjb2|pk7s8e0"sv, 0u }, }; for (auto& test : tests) { From 583ca6af89c532e2347f494e9192697c6d4d9ca7 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Wed, 11 Dec 2024 11:34:41 +0900 Subject: [PATCH 114/237] LibWeb: Implement experimentally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In conformance with the requirements of the spec PR at https://github.com/whatwg/html/pull/9546, this change adds support for the “switch” attribute for type=checkbox “input” elements — which is shipping in Safari (since Safari 17.4). This change also implements support for exposing it to AT users with role=switch. --- Libraries/LibWeb/CSS/Default.css | 35 +++++++++ Libraries/LibWeb/CSS/SelectorEngine.cpp | 4 +- Libraries/LibWeb/HTML/AttributeNames.cpp | 2 + Libraries/LibWeb/HTML/AttributeNames.h | 1 + Libraries/LibWeb/HTML/HTMLInputElement.cpp | 5 +- Libraries/LibWeb/HTML/HTMLInputElement.idl | 2 + .../BindingsGenerator/IDLGenerators.cpp | 2 +- .../roles-dynamic-switch.tentative.window.txt | 11 +++ ...-type-checkbox-switch.tentative.window.txt | 7 ++ ...input-checkbox-switch.tentative.window.txt | 11 +++ ...roles-dynamic-switch.tentative.window.html | 10 +++ .../roles-dynamic-switch.tentative.window.js | 71 ++++++++++++++++++ ...type-checkbox-switch.tentative.window.html | 8 ++ ...t-type-checkbox-switch.tentative.window.js | 19 +++++ ...nput-checkbox-switch.tentative.window.html | 8 ++ .../input-checkbox-switch.tentative.window.js | 75 +++++++++++++++++++ 16 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js diff --git a/Libraries/LibWeb/CSS/Default.css b/Libraries/LibWeb/CSS/Default.css index 256e641ff76bf..18941f090fdc1 100644 --- a/Libraries/LibWeb/CSS/Default.css +++ b/Libraries/LibWeb/CSS/Default.css @@ -857,3 +857,38 @@ progress { filter: invert(100%); } } + +/* https://github.com/whatwg/html/pull/9546 + */ +input[type=checkbox][switch] { + appearance: none; + height: 1em; + width: 1.8em; + vertical-align: middle; + border-radius: 1em; + position: relative; + overflow: hidden; + border-color: transparent; + background-color: ButtonFace; +} + +input[type=checkbox][switch]::before { + content: ''; + position: absolute; + height: 0; + width: 0; + border: .46em solid Field; + border-radius: 100%; + top: 0; + bottom: 0; + left: 0; + margin: auto; +} + +input[type=checkbox][switch]:checked::before { + left: calc(100% - .87em); +} + +input[type=checkbox][switch]:checked { + background-color: AccentColor; +} diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index eef6bfc313f62..dbb6f89009c8c 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -201,7 +201,9 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen auto const& input_element = static_cast(element); switch (input_element.type_state()) { case HTML::HTMLInputElement::TypeAttributeState::Checkbox: - return input_element.indeterminate(); + // https://whatpr.org/html-attr-input-switch/9546/semantics-other.html#selector-indeterminate + // input elements whose type attribute is in the Checkbox state, whose switch attribute is not set + return input_element.indeterminate() && !element.has_attribute(HTML::AttributeNames::switch_); default: return false; } diff --git a/Libraries/LibWeb/HTML/AttributeNames.cpp b/Libraries/LibWeb/HTML/AttributeNames.cpp index 2e73cb25113cc..5b0d4b0d06fcf 100644 --- a/Libraries/LibWeb/HTML/AttributeNames.cpp +++ b/Libraries/LibWeb/HTML/AttributeNames.cpp @@ -28,6 +28,7 @@ void initialize_strings() for_ = "for"_fly_string; default_ = "default"_fly_string; char_ = "char"_fly_string; + switch_ = "switch"_fly_string; // NOTE: Special cases for attributes with dashes in them. accept_charset = "accept-charset"_fly_string; @@ -81,6 +82,7 @@ bool is_boolean_attribute(FlyString const& attribute) || attribute.equals_ignoring_ascii_case(AttributeNames::reversed) || attribute.equals_ignoring_ascii_case(AttributeNames::seeking) || attribute.equals_ignoring_ascii_case(AttributeNames::selected) + || attribute.equals_ignoring_ascii_case(AttributeNames::switch_) || attribute.equals_ignoring_ascii_case(AttributeNames::truespeed) || attribute.equals_ignoring_ascii_case(AttributeNames::willvalidate); } diff --git a/Libraries/LibWeb/HTML/AttributeNames.h b/Libraries/LibWeb/HTML/AttributeNames.h index d82b5ddf479b7..f8f1a7bf0f9d9 100644 --- a/Libraries/LibWeb/HTML/AttributeNames.h +++ b/Libraries/LibWeb/HTML/AttributeNames.h @@ -278,6 +278,7 @@ namespace AttributeNames { __ENUMERATE_HTML_ATTRIBUTE(step) \ __ENUMERATE_HTML_ATTRIBUTE(style) \ __ENUMERATE_HTML_ATTRIBUTE(summary) \ + __ENUMERATE_HTML_ATTRIBUTE(switch_) \ __ENUMERATE_HTML_ATTRIBUTE(tabindex) \ __ENUMERATE_HTML_ATTRIBUTE(target) \ __ENUMERATE_HTML_ATTRIBUTE(text) \ diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index c8e9034664eb9..25d96221fa77a 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -2343,13 +2343,16 @@ void HTMLInputElement::set_custom_validity(String const& error) Optional HTMLInputElement::default_role() const { + // http://wpt.live/html-aam/roles-dynamic-switch.tentative.window.html "Disconnected " + if (!is_connected()) + return {}; // https://www.w3.org/TR/html-aria/#el-input-button if (type_state() == TypeAttributeState::Button) return ARIA::Role::button; // https://www.w3.org/TR/html-aria/#el-input-checkbox if (type_state() == TypeAttributeState::Checkbox) { // https://github.com/w3c/html-aam/issues/496 - if (has_attribute("switch"_string)) + if (has_attribute(HTML::AttributeNames::switch_)) return ARIA::Role::switch_; return ARIA::Role::checkbox; } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Libraries/LibWeb/HTML/HTMLInputElement.idl index c2824993ba71c..709d16c98d201 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.idl +++ b/Libraries/LibWeb/HTML/HTMLInputElement.idl @@ -38,6 +38,8 @@ interface HTMLInputElement : HTMLElement { [CEReactions] attribute unsigned long size; [CEReactions, Reflect, URL] attribute USVString src; [CEReactions, Reflect] attribute DOMString step; + // https://whatpr.org/html-attr-input-switch/9546/input.html#the-input-element:dom-input-switch + [CEReactions, Reflect] attribute boolean switch; [CEReactions] attribute DOMString type; [CEReactions, Reflect=value] attribute DOMString defaultValue; [CEReactions, LegacyNullToEmptyString] attribute DOMString value; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 356e559a870ca..8b546ca881699 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -288,7 +288,7 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface) static ByteString make_input_acceptable_cpp(ByteString const& input) { - if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register")) { + if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register", "switch")) { StringBuilder builder; builder.append(input); builder.append('_'); diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt new file mode 100644 index 0000000000000..7325207a8fefd --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass Disconnected +Pass Connected +Pass Connected : adding switch attribute +Pass Connected : removing switch attribute +Pass Connected : removing type attribute +Pass Connected : adding type attribute \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt new file mode 100644 index 0000000000000..3fd6388f6b9a1 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt @@ -0,0 +1,7 @@ +Harness status: OK + +Found 2 tests + +2 Pass +Pass switch IDL attribute, setter +Pass switch IDL attribute, getter \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt new file mode 100644 index 0000000000000..6beb495271477 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass Switch control does not match :indeterminate +Pass Checkbox that is no longer a switch control does match :indeterminate +Pass Checkbox that becomes a switch control does not match :indeterminate +Pass Parent of a checkbox that becomes a switch control does not match :has(:indeterminate) +Pass Parent of a switch control that becomes a checkbox continues to match :has(:checked) +Pass A switch control that becomes a checkbox in a roundabout way \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html new file mode 100644 index 0000000000000..ff6dd4e2fa6be --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html @@ -0,0 +1,10 @@ + + + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js new file mode 100644 index 0000000000000..2993c36764859 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js @@ -0,0 +1,71 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/resources/testdriver-actions.js + +promise_test(async () => { + const control = document.createElement("input"); + control.type = "checkbox"; + control.switch = true; + const role = await test_driver.get_computed_role(control); + assert_equals(role, ""); +}, `Disconnected `); + +promise_test(async t => { + const control = document.createElement("input"); + t.add_cleanup(() => control.remove()); + control.type = "checkbox"; + control.switch = true; + document.body.append(control); + const role = await test_driver.get_computed_role(control); + assert_equals(role, "switch"); +}, `Connected `); + +promise_test(async t => { + const control = document.createElement("input"); + t.add_cleanup(() => control.remove()); + control.type = "checkbox"; + document.body.append(control); + let role = await test_driver.get_computed_role(control); + assert_equals(role, "checkbox"); + control.switch = true; + role = await test_driver.get_computed_role(control); + assert_equals(role, "switch"); +}, `Connected : adding switch attribute`); + +promise_test(async t => { + const control = document.createElement("input"); + t.add_cleanup(() => control.remove()); + control.type = "checkbox"; + control.switch = true; + document.body.append(control); + let role = await test_driver.get_computed_role(control); + assert_equals(role, "switch"); + control.switch = false; + role = await test_driver.get_computed_role(control); + assert_equals(role, "checkbox"); +}, `Connected : removing switch attribute`); + +promise_test(async t => { + const control = document.createElement("input"); + t.add_cleanup(() => control.remove()); + control.type = "checkbox"; + document.body.append(control); + control.switch = true; + let role = await test_driver.get_computed_role(control); + assert_equals(role, "switch"); + control.removeAttribute("type"); + role = await test_driver.get_computed_role(control); + assert_equals(role, "textbox"); +}, `Connected : removing type attribute`); + +promise_test(async t => { + const control = document.createElement("input"); + t.add_cleanup(() => control.remove()); + control.switch = true; + document.body.append(control); + let role = await test_driver.get_computed_role(control); + assert_equals(role, "textbox"); + control.type = "checkbox"; + role = await test_driver.get_computed_role(control); + assert_equals(role, "switch"); +}, `Connected : adding type attribute`); diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html new file mode 100644 index 0000000000000..6cfe2938d2142 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html @@ -0,0 +1,8 @@ + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js new file mode 100644 index 0000000000000..6128a62a0fb0d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js @@ -0,0 +1,19 @@ +test(t => { + const input = document.createElement("input"); + input.switch = true; + + assert_true(input.hasAttribute("switch")); + assert_equals(input.getAttribute("switch"), ""); + assert_equals(input.type, "text"); +}, "switch IDL attribute, setter"); + +test(t => { + const container = document.createElement("div"); + container.innerHTML = ""; + const input = container.firstChild; + + assert_true(input.hasAttribute("switch")); + assert_equals(input.getAttribute("switch"), ""); + assert_equals(input.type, "checkbox"); + assert_true(input.switch); +}, "switch IDL attribute, getter"); diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html new file mode 100644 index 0000000000000..25f97e03540f4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html @@ -0,0 +1,8 @@ + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js new file mode 100644 index 0000000000000..b5d9898a640e9 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js @@ -0,0 +1,75 @@ +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.switch = true; + input.indeterminate = true; + + assert_false(input.matches(":indeterminate")); +}, "Switch control does not match :indeterminate"); + +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.switch = true; + input.indeterminate = true; + + assert_false(input.matches(":indeterminate")); + + input.switch = false; + assert_true(input.matches(":indeterminate")); +}, "Checkbox that is no longer a switch control does match :indeterminate"); + +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.indeterminate = true; + + assert_true(input.matches(":indeterminate")); + + input.setAttribute("switch", "blah"); + assert_false(input.matches(":indeterminate")); +}, "Checkbox that becomes a switch control does not match :indeterminate"); + +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.indeterminate = true; + + assert_true(document.body.matches(":has(:indeterminate)")); + + input.switch = true; + assert_false(document.body.matches(":has(:indeterminate)")); +}, "Parent of a checkbox that becomes a switch control does not match :has(:indeterminate)"); + +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.switch = true + input.checked = true; + + assert_true(document.body.matches(":has(:checked)")); + + input.switch = false; + assert_true(document.body.matches(":has(:checked)")); + + input.checked = false; + assert_false(document.body.matches(":has(:checked)")); +}, "Parent of a switch control that becomes a checkbox continues to match :has(:checked)"); + +test(t => { + const input = document.body.appendChild(document.createElement("input")); + t.add_cleanup(() => input.remove()); + input.type = "checkbox"; + input.switch = true; + input.indeterminate = true; + assert_false(input.matches(":indeterminate")); + input.type = "text"; + input.removeAttribute("switch"); + input.type = "checkbox"; + assert_true(input.matches(":indeterminate")); +}, "A switch control that becomes a checkbox in a roundabout way"); From 84150f972f9df908c5cac5175ac165b117055cea Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:44:14 +1100 Subject: [PATCH 115/237] LibWeb: Properly serialize position/edge style values --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 59 ++- .../CSS/ResolvedCSSStyleDeclaration.cpp | 27 -- Libraries/LibWeb/CSS/StyleProperties.cpp | 4 +- .../CSS/StyleValues/BasicShapeStyleValue.cpp | 22 +- .../CSS/StyleValues/BasicShapeStyleValue.h | 12 +- .../LibWeb/CSS/StyleValues/EdgeStyleValue.cpp | 35 +- .../LibWeb/CSS/StyleValues/EdgeStyleValue.h | 30 +- .../CSS/StyleValues/PositionStyleValue.cpp | 6 +- .../CSS/StyleValues/PositionStyleValue.h | 4 +- .../CSS/StyleValues/ShorthandStyleValue.cpp | 39 +- Libraries/LibWeb/Layout/Node.cpp | 4 +- .../expected/HTML/background-shorthand.txt | 2 +- .../Text/expected/background-position-xy.txt | 4 +- ...upported-properties-and-default-values.txt | 18 +- .../Text/expected/css/calc-coverage.txt | 8 +- .../css/getComputedStyle-print-all.txt | 6 +- .../Text/expected/position-serialization.txt | 18 + .../parsing/background-position-valid.txt | 36 ++ .../wpt-import/css/cssom/serialize-values.txt | 342 +++++++++--------- .../Text/input/position-serialization.html | 25 ++ .../parsing/background-position-valid.html | 48 +++ 21 files changed, 461 insertions(+), 288 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/position-serialization.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt create mode 100644 Tests/LibWeb/Text/input/position-serialization.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 55f0988eb7d40..ca7932a8cd605 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3913,12 +3913,6 @@ RefPtr Parser::parse_position_value(TokenStream NonnullRefPtr { - if (position_edge == PositionEdge::Center) - return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); - return EdgeStyleValue::create(position_edge, Length::make_px(0)); - }; - // = [ // [ left | center | right | top | bottom | ] // | @@ -3945,21 +3939,21 @@ RefPtr Parser::parse_position_value(TokenStream ] if (auto maybe_percentage = parse_length_percentage(token); maybe_percentage.has_value()) { transaction.commit(); - return PositionStyleValue::create(EdgeStyleValue::create(PositionEdge::Left, *maybe_percentage), make_edge_style_value(PositionEdge::Center, false)); + return PositionStyleValue::create(EdgeStyleValue::create({}, *maybe_percentage), EdgeStyleValue::create(PositionEdge::Center, {})); } return nullptr; @@ -3994,7 +3988,7 @@ RefPtr Parser::parse_position_value(TokenStream Parser::parse_position_value(TokenStream ] @@ -4157,15 +4151,15 @@ RefPtr Parser::parse_position_value(TokenStream NonnullRefPtr { + auto to_style_value = [&](PositionAndMaybeLength const& group) -> NonnullRefPtr { if (group.position == PositionEdge::Center) - return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); + return EdgeStyleValue::create(PositionEdge::Center, {}); - return EdgeStyleValue::create(group.position, group.length.value_or(Length::make_px(0))); + return EdgeStyleValue::create(group.position, group.length); }; transaction.commit(); - return PositionStyleValue::create(to_style_value(group1, true), to_style_value(group2, false)); + return PositionStyleValue::create(to_style_value(group1), to_style_value(group2)); }; // Note: The alternatives must be attempted in this order since shorter alternatives can match a prefix of longer ones. @@ -4527,16 +4521,7 @@ static Optional style_value_to_length_percentage(auto value) RefPtr Parser::parse_single_background_position_x_or_y_value(TokenStream& tokens, PropertyID property) { - PositionEdge relative_edge {}; - if (property == PropertyID::BackgroundPositionX) { - // [ center | [ [ left | right | x-start | x-end ]? ? ]! ]# - relative_edge = PositionEdge::Left; - } else if (property == PropertyID::BackgroundPositionY) { - // [ center | [ [ top | bottom | y-start | y-end ]? ? ]! ]# - relative_edge = PositionEdge::Top; - } else { - VERIFY_NOT_REACHED(); - } + Optional relative_edge {}; auto transaction = tokens.begin_transaction(); if (!tokens.has_next_token()) @@ -4550,10 +4535,10 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke auto keyword = value->to_keyword(); if (keyword == Keyword::Center) { transaction.commit(); - return EdgeStyleValue::create(relative_edge, Percentage { 50 }); + return EdgeStyleValue::create(PositionEdge::Center, {}); } if (auto edge = keyword_to_position_edge(keyword); edge.has_value()) { - relative_edge = *edge; + relative_edge = edge; } else { return nullptr; } @@ -4561,7 +4546,7 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke value = parse_css_value_for_property(property, tokens); if (!value) { transaction.commit(); - return EdgeStyleValue::create(relative_edge, Length::make_px(0)); + return EdgeStyleValue::create(relative_edge, {}); } } } @@ -4572,9 +4557,21 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke return EdgeStyleValue::create(relative_edge, *offset); } + if (!relative_edge.has_value()) { + if (property == PropertyID::BackgroundPositionX) { + // [ center | [ [ left | right | x-start | x-end ]? ? ]! ]# + relative_edge = PositionEdge::Left; + } else if (property == PropertyID::BackgroundPositionY) { + // [ center | [ [ top | bottom | y-start | y-end ]? ? ]! ]# + relative_edge = PositionEdge::Top; + } else { + VERIFY_NOT_REACHED(); + } + } + // If no offset is provided create this element but with an offset of default value of zero transaction.commit(); - return EdgeStyleValue::create(relative_edge, Length::make_px(0)); + return EdgeStyleValue::create(relative_edge, {}); } RefPtr Parser::parse_single_background_repeat_value(TokenStream& tokens) diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 2e0dfdf94d7d5..c2ffff6d0b975 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -82,20 +82,6 @@ String ResolvedCSSStyleDeclaration::item(size_t index) const return string_from_property_id(property_id).to_string(); } -static NonnullRefPtr style_value_for_background_property(Layout::NodeWithStyle const& layout_node, Function(BackgroundLayerData const&)> callback, Function()> default_value) -{ - auto const& background_layers = layout_node.background_layers(); - if (background_layers.is_empty()) - return default_value(); - if (background_layers.size() == 1) - return callback(background_layers.first()); - StyleValueVector values; - values.ensure_capacity(background_layers.size()); - for (auto const& layer : background_layers) - values.unchecked_append(callback(layer)); - return StyleValueList::create(move(values), StyleValueList::Separator::Comma); -} - static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) { if (length_percentage.is_auto()) @@ -453,19 +439,6 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // NOTE: This is handled inside the `default` case. // NOTE: Everything below is a shorthand that requires some manual construction. - case PropertyID::BackgroundPosition: - return style_value_for_background_property( - layout_node, - [](auto& layer) -> NonnullRefPtr { - return PositionStyleValue::create( - EdgeStyleValue::create(layer.position_edge_x, layer.position_offset_x), - EdgeStyleValue::create(layer.position_edge_y, layer.position_offset_y)); - }, - []() -> NonnullRefPtr { - return PositionStyleValue::create( - EdgeStyleValue::create(PositionEdge::Left, Percentage(0)), - EdgeStyleValue::create(PositionEdge::Top, Percentage(0))); - }); case PropertyID::Border: { auto width = style_value_for_property(layout_node, PropertyID::BorderWidth); auto style = style_value_for_property(layout_node, PropertyID::BorderStyle); diff --git a/Libraries/LibWeb/CSS/StyleProperties.cpp b/Libraries/LibWeb/CSS/StyleProperties.cpp index 2e452988afcdc..b67f09e9cde71 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -1325,12 +1325,12 @@ CSS::ObjectPosition StyleProperties::object_position() const auto const& edge_y = position.edge_y(); if (edge_x->is_edge()) { auto const& edge = edge_x->as_edge(); - object_position.edge_x = edge.edge(); + object_position.edge_x = edge.edge().value_or(PositionEdge::Left); object_position.offset_x = edge.offset(); } if (edge_y->is_edge()) { auto const& edge = edge_y->as_edge(); - object_position.edge_y = edge.edge(); + object_position.edge_y = edge.edge().value_or(PositionEdge::Top); object_position.offset_y = edge.offset(); } return object_position; diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp index f496b59038ccd..f64171bf94086 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp @@ -34,7 +34,7 @@ Gfx::Path Inset::to_path(CSSPixelRect reference_box, Layout::Node const& node) c return path_from_resolved_rect(top, right, bottom, left); } -String Inset::to_string() const +String Inset::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("inset({} {} {} {})", inset_box.top(), inset_box.right(), inset_box.bottom(), inset_box.left())); } @@ -49,7 +49,7 @@ Gfx::Path Xywh::to_path(CSSPixelRect reference_box, Layout::Node const& node) co return path_from_resolved_rect(top, right, bottom, left); } -String Xywh::to_string() const +String Xywh::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("xywh({} {} {} {})", x, y, width, height)); } @@ -68,7 +68,7 @@ Gfx::Path Rect::to_path(CSSPixelRect reference_box, Layout::Node const& node) co return path_from_resolved_rect(top, max(right, left), max(bottom, top), left); } -String Rect::to_string() const +String Rect::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("rect({} {} {} {})", box.top(), box.right(), box.bottom(), box.left())); } @@ -123,9 +123,9 @@ Gfx::Path Circle::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Circle::to_string() const +String Circle::to_string(CSSStyleValue::SerializationMode mode) const { - return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(CSSStyleValue::SerializationMode::Normal))); + return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(mode))); } Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) const @@ -168,9 +168,9 @@ Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Ellipse::to_string() const +String Ellipse::to_string(CSSStyleValue::SerializationMode mode) const { - return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(CSSStyleValue::SerializationMode::Normal))); + return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(mode))); } Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) const @@ -193,7 +193,7 @@ Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Polygon::to_string() const +String Polygon::to_string(CSSStyleValue::SerializationMode) const { StringBuilder builder; builder.append("polygon("sv); @@ -220,10 +220,10 @@ Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node }); } -String BasicShapeStyleValue::to_string(SerializationMode) const +String BasicShapeStyleValue::to_string(SerializationMode mode) const { - return m_basic_shape.visit([](auto const& shape) { - return shape.to_string(); + return m_basic_shape.visit([mode](auto const& shape) { + return shape.to_string(mode); }); } diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h index 529a18201943c..e9ec143b8a72d 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h @@ -17,7 +17,7 @@ namespace Web::CSS { struct Inset { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Inset const&) const = default; @@ -26,7 +26,7 @@ struct Inset { struct Xywh { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Xywh const&) const = default; @@ -38,7 +38,7 @@ struct Xywh { struct Rect { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Rect const&) const = default; @@ -54,7 +54,7 @@ using ShapeRadius = Variant; struct Circle { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Circle const&) const = default; @@ -64,7 +64,7 @@ struct Circle { struct Ellipse { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Ellipse const&) const = default; @@ -81,7 +81,7 @@ struct Polygon { }; Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Polygon const&) const = default; diff --git a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp index 50345f10b49b1..8b3af47738664 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp @@ -8,9 +8,40 @@ namespace Web::CSS { -String EdgeStyleValue::to_string(SerializationMode) const +String EdgeStyleValue::to_string(SerializationMode mode) const { - return MUST(String::formatted("{} {}", CSS::to_string(m_properties.edge), m_properties.offset.to_string())); + if (mode == CSSStyleValue::SerializationMode::ResolvedValue) { + if (edge() == PositionEdge::Right || edge() == PositionEdge::Bottom) { + if (offset().is_percentage()) { + auto flipped_percentage = 100 - offset().percentage().value(); + return Percentage(flipped_percentage).to_string(); + } + Vector> sum_parts; + sum_parts.append(NumericCalculationNode::create(Percentage(100))); + if (offset().is_length()) { + sum_parts.append(NegateCalculationNode::create(NumericCalculationNode::create(offset().length()))); + } else { + // FIXME: Flip calculated offsets (convert CSSMathValue to CalculationNode, then negate and append) + return to_string(CSSStyleValue::SerializationMode::Normal); + } + auto flipped_absolute = CSSMathValue::create(SumCalculationNode::create(move(sum_parts)), CSSNumericType(CSSNumericType::BaseType::Length, 1)); + return flipped_absolute->to_string(mode); + } + return offset().to_string(); + } + + StringBuilder builder; + + if (m_properties.edge.has_value()) + builder.append(CSS::to_string(m_properties.edge.value())); + + if (m_properties.edge.has_value() && m_properties.offset.has_value()) + builder.append(' '); + + if (m_properties.offset.has_value()) + builder.append(m_properties.offset->to_string()); + + return builder.to_string_without_validation(); } } diff --git a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h index 5e81d6ea705d0..fa9bdc3d83309 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h @@ -14,31 +14,45 @@ namespace Web::CSS { class EdgeStyleValue final : public StyleValueWithDefaultOperators { public: - static ValueComparingNonnullRefPtr create(PositionEdge edge, LengthPercentage const& offset) + static ValueComparingNonnullRefPtr create(Optional edge, Optional const& offset) { - VERIFY(edge != PositionEdge::Center); return adopt_ref(*new (nothrow) EdgeStyleValue(edge, offset)); } virtual ~EdgeStyleValue() override = default; - // NOTE: `center` is converted to `left 50%` or `top 50%` in parsing, so is never returned here. - PositionEdge edge() const { return m_properties.edge; } - LengthPercentage const& offset() const { return m_properties.offset; } + Optional edge() const + { + if (m_properties.edge == PositionEdge::Center) + return {}; + + return m_properties.edge; + } + + LengthPercentage offset() const + { + if (m_properties.edge == PositionEdge::Center) + return Percentage(50); + + if (!m_properties.offset.has_value()) + return Percentage(0); + + return m_properties.offset.value(); + } virtual String to_string(SerializationMode) const override; bool properties_equal(EdgeStyleValue const& other) const { return m_properties == other.m_properties; } private: - EdgeStyleValue(PositionEdge edge, LengthPercentage const& offset) + EdgeStyleValue(Optional edge, Optional const& offset) : StyleValueWithDefaultOperators(Type::Edge) , m_properties { .edge = edge, .offset = offset } { } struct Properties { - PositionEdge edge; - LengthPercentage offset; + Optional edge; + Optional offset; bool operator==(Properties const&) const = default; } m_properties; }; diff --git a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp index 250ee6e5b161e..f470aa046031e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp @@ -13,10 +13,8 @@ namespace Web::CSS { bool PositionStyleValue::is_center() const { - return (edge_x()->edge() == PositionEdge::Left - && edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 }) - && (edge_y()->edge() == PositionEdge::Top - && edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 }); + return (edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 }) + && (edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 }); } CSSPixelPoint PositionStyleValue::resolved(Layout::Node const& node, CSSPixelRect const& rect) const diff --git a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h index cb212326711f2..dcd3c10b2802c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h @@ -25,8 +25,8 @@ class PositionStyleValue final : public StyleValueWithDefaultOperators create_center() { return adopt_ref(*new (nothrow) PositionStyleValue( - EdgeStyleValue::create(PositionEdge::Left, Percentage { 50 }), - EdgeStyleValue::create(PositionEdge::Top, Percentage { 50 }))); + EdgeStyleValue::create(PositionEdge::Center, {}), + EdgeStyleValue::create(PositionEdge::Center, {}))); } virtual ~PositionStyleValue() override = default; diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 9f87276b57868..5f9963e224036 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -65,6 +65,8 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const auto color = longhand(PropertyID::BackgroundColor); auto image = longhand(PropertyID::BackgroundImage); auto position = longhand(PropertyID::BackgroundPosition); + auto position_x = position->as_shorthand().longhand(PropertyID::BackgroundPositionX); + auto position_y = position->as_shorthand().longhand(PropertyID::BackgroundPositionY); auto size = longhand(PropertyID::BackgroundSize); auto repeat = longhand(PropertyID::BackgroundRepeat); auto attachment = longhand(PropertyID::BackgroundAttachment); @@ -75,10 +77,10 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const return style_value->is_value_list() ? style_value->as_value_list().size() : 1; }; - auto layer_count = max(get_layer_count(image), max(get_layer_count(position), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip))))))); + auto layer_count = max(get_layer_count(image), max(get_layer_count(position_x), max(get_layer_count(position_y), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip)))))))); if (layer_count == 1) { - return MUST(String::formatted("{} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode))); + return MUST(String::formatted("{} {} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position_x->to_string(mode), position_y->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode))); } auto get_layer_value_string = [mode](ValueComparingRefPtr const& style_value, size_t index) { @@ -93,7 +95,38 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const builder.append(", "sv); if (i == layer_count - 1) builder.appendff("{} ", color->to_string(mode)); - builder.appendff("{} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i)); + builder.appendff("{} {} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position_x, i), get_layer_value_string(position_y, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i)); + } + + return MUST(builder.to_string()); + } + case Web::CSS::PropertyID::BackgroundPosition: { + auto x_edges = longhand(PropertyID::BackgroundPositionX); + auto y_edges = longhand(PropertyID::BackgroundPositionY); + + auto get_layer_count = [](auto style_value) -> size_t { + return style_value->is_value_list() ? style_value->as_value_list().size() : 1; + }; + + // FIXME: The spec is unclear about how differing layer counts should be handled + auto layer_count = max(get_layer_count(x_edges), get_layer_count(y_edges)); + + if (layer_count == 1) { + return MUST(String::formatted("{} {}", x_edges->to_string(mode), y_edges->to_string(mode))); + } + + auto get_layer_value_string = [mode](ValueComparingRefPtr const& style_value, size_t index) { + if (style_value->is_value_list()) + return style_value->as_value_list().value_at(index, true)->to_string(mode); + return style_value->to_string(mode); + }; + + StringBuilder builder; + for (size_t i = 0; i < layer_count; i++) { + if (i) + builder.append(", "sv); + + builder.appendff("{} {}", get_layer_value_string(x_edges, i), get_layer_value_string(y_edges, i)); } return MUST(builder.to_string()); diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index ec54f02dd4ceb..a92d2329466ab 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -428,13 +428,13 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto position_value = value_for_layer(x_positions, layer_index); position_value && position_value->is_edge()) { auto& position = position_value->as_edge(); - layer.position_edge_x = position.edge(); + layer.position_edge_x = position.edge().value_or(CSS::PositionEdge::Left); layer.position_offset_x = position.offset(); } if (auto position_value = value_for_layer(y_positions, layer_index); position_value && position_value->is_edge()) { auto& position = position_value->as_edge(); - layer.position_edge_y = position.edge(); + layer.position_edge_y = position.edge().value_or(CSS::PositionEdge::Top); layer.position_offset_y = position.offset(); }; diff --git a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt index 5100c84795bbd..8f3128cc4297c 100644 --- a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt +++ b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt @@ -1,4 +1,4 @@ -style.cssText = background-color: yellow; background-image: none; background-position-x: left 0%; background-position-y: top 0%; background-size: auto auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box; +style.cssText = background-color: yellow; background-image: none; background-position-x: 0%; background-position-y: 0%; background-size: auto auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box; style.length = 9 style[] = 1. background-color diff --git a/Tests/LibWeb/Text/expected/background-position-xy.txt b/Tests/LibWeb/Text/expected/background-position-xy.txt index dd4e27cb9fb5f..077355b134da3 100644 --- a/Tests/LibWeb/Text/expected/background-position-xy.txt +++ b/Tests/LibWeb/Text/expected/background-position-xy.txt @@ -1,2 +1,2 @@ -right 0px -bottom 0px +100% +100% diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt index 7bf960ade92e7..b4a657217feae 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt @@ -159,7 +159,7 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'aspect-ratio': 'auto' 'backdropFilter': 'none' 'backdrop-filter': 'none' -'background': 'rgba(0, 0, 0, 0) none left 0% top 0% auto auto repeat scroll padding-box border-box' +'background': 'rgba(0, 0, 0, 0) none 0% 0% auto auto repeat scroll padding-box border-box' 'backgroundAttachment': 'scroll' 'background-attachment': 'scroll' 'backgroundClip': 'border-box' @@ -170,12 +170,12 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'background-image': 'none' 'backgroundOrigin': 'padding-box' 'background-origin': 'padding-box' -'backgroundPosition': 'left 0% top 0%' -'background-position': 'left 0% top 0%' -'backgroundPositionX': 'left 0%' -'background-position-x': 'left 0%' -'backgroundPositionY': 'top 0%' -'background-position-y': 'top 0%' +'backgroundPosition': '0% 0%' +'background-position': '0% 0%' +'backgroundPositionX': '0%' +'background-position-x': '0%' +'backgroundPositionY': '0%' +'background-position-y': '0%' 'backgroundRepeat': 'repeat' 'background-repeat': 'repeat' 'backgroundSize': 'auto auto' @@ -430,8 +430,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'min-width': 'auto' 'objectFit': 'fill' 'object-fit': 'fill' -'objectPosition': 'left 50% top 50%' -'object-position': 'left 50% top 50%' +'objectPosition': '50% 50%' +'object-position': '50% 50%' 'opacity': '1' 'order': '0' 'outline': 'rgb(0, 0, 0) none medium' diff --git a/Tests/LibWeb/Text/expected/css/calc-coverage.txt b/Tests/LibWeb/Text/expected/css/calc-coverage.txt index fc2f6a083a276..0e254b85def64 100644 --- a/Tests/LibWeb/Text/expected/css/calc-coverage.txt +++ b/Tests/LibWeb/Text/expected/css/calc-coverage.txt @@ -8,10 +8,10 @@ backdrop-filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))' backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))' backdrop-filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))' backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))' -background-position-x: 'calc(2px)' -> 'left calc(2px)' -background-position-x: 'calc(2px * var(--n))' -> 'left calc(2px * 2)' -background-position-y: 'calc(2%)' -> 'top calc(2%)' -background-position-y: 'calc(2% * var(--n))' -> 'top 4%' +background-position-x: 'calc(2px)' -> 'calc(2px)' +background-position-x: 'calc(2px * var(--n))' -> 'calc(2px * 2)' +background-position-y: 'calc(2%)' -> 'calc(2%)' +background-position-y: 'calc(2% * var(--n))' -> '4%' background-size: 'calc(2px * var(--n)) calc(2%)' -> 'calc(2px * 2) 2%' background-size: 'calc(2px * var(--n)) calc(2% * var(--n))' -> 'calc(2px * 2) 4%' border-bottom-left-radius: 'calc(2px)' -> 'calc(2px)' diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index 175a3646fcc89..dce8309831166 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -71,8 +71,8 @@ background-clip: border-box background-color: rgba(0, 0, 0, 0) background-image: none background-origin: padding-box -background-position-x: left 0% -background-position-y: top 0% +background-position-x: 0% +background-position-y: 0% background-repeat: repeat background-size: auto auto border-bottom-color: rgb(0, 0, 0) @@ -154,7 +154,7 @@ min-height: auto min-inline-size: 0px min-width: auto object-fit: fill -object-position: left 50% top 50% +object-position: 50% 50% opacity: 1 order: 0 outline-color: rgb(0, 0, 0) diff --git a/Tests/LibWeb/Text/expected/position-serialization.txt b/Tests/LibWeb/Text/expected/position-serialization.txt new file mode 100644 index 0000000000000..cb505b6ee409b --- /dev/null +++ b/Tests/LibWeb/Text/expected/position-serialization.txt @@ -0,0 +1,18 @@ +inline: center center +computed: 50% 50% +inline: left bottom +computed: 0% 100% +inline: center top +computed: 50% 0% +inline: center top 20% +computed: 50% 20% +inline: left 10px top 20% +computed: 10px 20% +inline: 10px top +computed: 10px 0% +inline: right 10px bottom 20% +computed: calc(100% + (0 - 10px)) 80% +inline: center center, left bottom +computed: 50% 50%, 0% 100% +inline: left 10px bottom 20%, right 10px top 20% +computed: 10px 80%, calc(100% + (0 - 10px)) 20% diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt new file mode 100644 index 0000000000000..1927ba08ce49d --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt @@ -0,0 +1,36 @@ +Harness status: OK + +Found 31 tests + +31 Pass +Pass e.style['background-position'] = "1px" should set the property value +Pass e.style['background-position'] = "1px center" should set the property value +Pass e.style['background-position'] = "-2% -3%" should set the property value +Pass e.style['background-position'] = "5% top" should set the property value +Pass e.style['background-position'] = "center" should set the property value +Pass e.style['background-position'] = "center center" should set the property value +Pass e.style['background-position'] = "center 6px" should set the property value +Pass e.style['background-position'] = "center left" should set the property value +Pass e.style['background-position'] = "center right 7%" should set the property value +Pass e.style['background-position'] = "center bottom" should set the property value +Pass e.style['background-position'] = "center top 8px" should set the property value +Pass e.style['background-position'] = "left" should set the property value +Pass e.style['background-position'] = "right 9%" should set the property value +Pass e.style['background-position'] = "left 10px center" should set the property value +Pass e.style['background-position'] = "right 11% bottom" should set the property value +Pass e.style['background-position'] = "left 12px top 13px" should set the property value +Pass e.style['background-position'] = "right center" should set the property value +Pass e.style['background-position'] = "left bottom" should set the property value +Pass e.style['background-position'] = "right top 14%" should set the property value +Pass e.style['background-position'] = "bottom" should set the property value +Pass e.style['background-position'] = "top 15px center" should set the property value +Pass e.style['background-position'] = "bottom 16% left" should set the property value +Pass e.style['background-position'] = "top 17px right 18px" should set the property value +Pass e.style['background-position'] = "bottom center" should set the property value +Pass e.style['background-position'] = "top left" should set the property value +Pass e.style['background-position'] = "bottom right 19%" should set the property value +Pass e.style['background-position'] = "20% 0%" should set the property value +Pass e.style['background-position'] = "0% 0%" should set the property value +Pass e.style['background-position'] = "0%" should set the property value +Pass e.style['background-position'] = "0% center" should set the property value +Pass e.style['background-position'] = "center 0%" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt index a7b5f000635ad..702473bde5285 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt @@ -2,8 +2,8 @@ Harness status: OK Found 687 tests -485 Pass -202 Fail +654 Pass +33 Fail Pass background-attachment: scroll Pass background-attachment: fixed Pass background-attachment: inherit @@ -17,175 +17,175 @@ Pass background-image: url("http://localhost/") Pass background-image: url(http://localhost/) Pass background-image: none Pass background-image: inherit -Fail background-position: 5% 5% -Fail background-position: 5% .5% -Fail background-position: 5% -5% -Fail background-position: 5% -.5% -Fail background-position: 5% 0px -Fail background-position: 5% 1px -Fail background-position: 5% .1em -Fail background-position: 5% -0px -Fail background-position: 5% -1px -Fail background-position: 5% -.1em -Fail background-position: 5% top -Fail background-position: 5% center -Fail background-position: 5% bottom -Fail background-position: .5% 5% -Fail background-position: .5% .5% -Fail background-position: .5% -5% -Fail background-position: .5% -.5% -Fail background-position: .5% 0px -Fail background-position: .5% 1px -Fail background-position: .5% .1em -Fail background-position: .5% -0px -Fail background-position: .5% -1px -Fail background-position: .5% -.1em -Fail background-position: .5% top -Fail background-position: .5% center -Fail background-position: .5% bottom -Fail background-position: -5% 5% -Fail background-position: -5% .5% -Fail background-position: -5% -5% -Fail background-position: -5% -.5% -Fail background-position: -5% 0px -Fail background-position: -5% 1px -Fail background-position: -5% .1em -Fail background-position: -5% -0px -Fail background-position: -5% -1px -Fail background-position: -5% -.1em -Fail background-position: -5% top -Fail background-position: -5% center -Fail background-position: -5% bottom -Fail background-position: -.5% 5% -Fail background-position: -.5% .5% -Fail background-position: -.5% -5% -Fail background-position: -.5% -.5% -Fail background-position: -.5% 0px -Fail background-position: -.5% 1px -Fail background-position: -.5% .1em -Fail background-position: -.5% -0px -Fail background-position: -.5% -1px -Fail background-position: -.5% -.1em -Fail background-position: -.5% top -Fail background-position: -.5% center -Fail background-position: -.5% bottom -Fail background-position: 0px 5% -Fail background-position: 0px .5% -Fail background-position: 0px -5% -Fail background-position: 0px -.5% -Fail background-position: 0px 0px -Fail background-position: 0px 1px -Fail background-position: 0px .1em -Fail background-position: 0px -0px -Fail background-position: 0px -1px -Fail background-position: 0px -.1em -Fail background-position: 0px top -Fail background-position: 0px center -Fail background-position: 0px bottom -Fail background-position: 1px 5% -Fail background-position: 1px .5% -Fail background-position: 1px -5% -Fail background-position: 1px -.5% -Fail background-position: 1px 0px -Fail background-position: 1px 1px -Fail background-position: 1px .1em -Fail background-position: 1px -0px -Fail background-position: 1px -1px -Fail background-position: 1px -.1em -Fail background-position: 1px top -Fail background-position: 1px center -Fail background-position: 1px bottom -Fail background-position: .1em 5% -Fail background-position: .1em .5% -Fail background-position: .1em -5% -Fail background-position: .1em -.5% -Fail background-position: .1em 0px -Fail background-position: .1em 1px -Fail background-position: .1em .1em -Fail background-position: .1em -0px -Fail background-position: .1em -1px -Fail background-position: .1em -.1em -Fail background-position: .1em top -Fail background-position: .1em center -Fail background-position: .1em bottom -Fail background-position: -0px 5% -Fail background-position: -0px .5% -Fail background-position: -0px -5% -Fail background-position: -0px -.5% -Fail background-position: -0px 0px -Fail background-position: -0px 1px -Fail background-position: -0px .1em -Fail background-position: -0px -0px -Fail background-position: -0px -1px -Fail background-position: -0px -.1em -Fail background-position: -0px top -Fail background-position: -0px center -Fail background-position: -0px bottom -Fail background-position: -1px 5% -Fail background-position: -1px .5% -Fail background-position: -1px -5% -Fail background-position: -1px -.5% -Fail background-position: -1px 0px -Fail background-position: -1px 1px -Fail background-position: -1px .1em -Fail background-position: -1px -0px -Fail background-position: -1px -1px -Fail background-position: -1px -.1em -Fail background-position: -1px top -Fail background-position: -1px center -Fail background-position: -1px bottom -Fail background-position: -.1em 5% -Fail background-position: -.1em .5% -Fail background-position: -.1em -5% -Fail background-position: -.1em -.5% -Fail background-position: -.1em 0px -Fail background-position: -.1em 1px -Fail background-position: -.1em .1em -Fail background-position: -.1em -0px -Fail background-position: -.1em -1px -Fail background-position: -.1em -.1em -Fail background-position: -.1em top -Fail background-position: -.1em center -Fail background-position: -.1em bottom -Fail background-position: left 5% -Fail background-position: left .5% -Fail background-position: left -5% -Fail background-position: left -.5% -Fail background-position: left 0px -Fail background-position: left 1px -Fail background-position: left .1em -Fail background-position: left -0px -Fail background-position: left -1px -Fail background-position: left -.1em -Fail background-position: left top -Fail background-position: left center -Fail background-position: left bottom -Fail background-position: center 5% -Fail background-position: center .5% -Fail background-position: center -5% -Fail background-position: center -.5% -Fail background-position: center 0px -Fail background-position: center 1px -Fail background-position: center .1em -Fail background-position: center -0px -Fail background-position: center -1px -Fail background-position: center -.1em -Fail background-position: center top -Fail background-position: center center -Fail background-position: center bottom -Fail background-position: right 5% -Fail background-position: right .5% -Fail background-position: right -5% -Fail background-position: right -.5% -Fail background-position: right 0px -Fail background-position: right 1px -Fail background-position: right .1em -Fail background-position: right -0px -Fail background-position: right -1px -Fail background-position: right -.1em -Fail background-position: right top -Fail background-position: right center -Fail background-position: right bottom +Pass background-position: 5% 5% +Pass background-position: 5% .5% +Pass background-position: 5% -5% +Pass background-position: 5% -.5% +Pass background-position: 5% 0px +Pass background-position: 5% 1px +Pass background-position: 5% .1em +Pass background-position: 5% -0px +Pass background-position: 5% -1px +Pass background-position: 5% -.1em +Pass background-position: 5% top +Pass background-position: 5% center +Pass background-position: 5% bottom +Pass background-position: .5% 5% +Pass background-position: .5% .5% +Pass background-position: .5% -5% +Pass background-position: .5% -.5% +Pass background-position: .5% 0px +Pass background-position: .5% 1px +Pass background-position: .5% .1em +Pass background-position: .5% -0px +Pass background-position: .5% -1px +Pass background-position: .5% -.1em +Pass background-position: .5% top +Pass background-position: .5% center +Pass background-position: .5% bottom +Pass background-position: -5% 5% +Pass background-position: -5% .5% +Pass background-position: -5% -5% +Pass background-position: -5% -.5% +Pass background-position: -5% 0px +Pass background-position: -5% 1px +Pass background-position: -5% .1em +Pass background-position: -5% -0px +Pass background-position: -5% -1px +Pass background-position: -5% -.1em +Pass background-position: -5% top +Pass background-position: -5% center +Pass background-position: -5% bottom +Pass background-position: -.5% 5% +Pass background-position: -.5% .5% +Pass background-position: -.5% -5% +Pass background-position: -.5% -.5% +Pass background-position: -.5% 0px +Pass background-position: -.5% 1px +Pass background-position: -.5% .1em +Pass background-position: -.5% -0px +Pass background-position: -.5% -1px +Pass background-position: -.5% -.1em +Pass background-position: -.5% top +Pass background-position: -.5% center +Pass background-position: -.5% bottom +Pass background-position: 0px 5% +Pass background-position: 0px .5% +Pass background-position: 0px -5% +Pass background-position: 0px -.5% +Pass background-position: 0px 0px +Pass background-position: 0px 1px +Pass background-position: 0px .1em +Pass background-position: 0px -0px +Pass background-position: 0px -1px +Pass background-position: 0px -.1em +Pass background-position: 0px top +Pass background-position: 0px center +Pass background-position: 0px bottom +Pass background-position: 1px 5% +Pass background-position: 1px .5% +Pass background-position: 1px -5% +Pass background-position: 1px -.5% +Pass background-position: 1px 0px +Pass background-position: 1px 1px +Pass background-position: 1px .1em +Pass background-position: 1px -0px +Pass background-position: 1px -1px +Pass background-position: 1px -.1em +Pass background-position: 1px top +Pass background-position: 1px center +Pass background-position: 1px bottom +Pass background-position: .1em 5% +Pass background-position: .1em .5% +Pass background-position: .1em -5% +Pass background-position: .1em -.5% +Pass background-position: .1em 0px +Pass background-position: .1em 1px +Pass background-position: .1em .1em +Pass background-position: .1em -0px +Pass background-position: .1em -1px +Pass background-position: .1em -.1em +Pass background-position: .1em top +Pass background-position: .1em center +Pass background-position: .1em bottom +Pass background-position: -0px 5% +Pass background-position: -0px .5% +Pass background-position: -0px -5% +Pass background-position: -0px -.5% +Pass background-position: -0px 0px +Pass background-position: -0px 1px +Pass background-position: -0px .1em +Pass background-position: -0px -0px +Pass background-position: -0px -1px +Pass background-position: -0px -.1em +Pass background-position: -0px top +Pass background-position: -0px center +Pass background-position: -0px bottom +Pass background-position: -1px 5% +Pass background-position: -1px .5% +Pass background-position: -1px -5% +Pass background-position: -1px -.5% +Pass background-position: -1px 0px +Pass background-position: -1px 1px +Pass background-position: -1px .1em +Pass background-position: -1px -0px +Pass background-position: -1px -1px +Pass background-position: -1px -.1em +Pass background-position: -1px top +Pass background-position: -1px center +Pass background-position: -1px bottom +Pass background-position: -.1em 5% +Pass background-position: -.1em .5% +Pass background-position: -.1em -5% +Pass background-position: -.1em -.5% +Pass background-position: -.1em 0px +Pass background-position: -.1em 1px +Pass background-position: -.1em .1em +Pass background-position: -.1em -0px +Pass background-position: -.1em -1px +Pass background-position: -.1em -.1em +Pass background-position: -.1em top +Pass background-position: -.1em center +Pass background-position: -.1em bottom +Pass background-position: left 5% +Pass background-position: left .5% +Pass background-position: left -5% +Pass background-position: left -.5% +Pass background-position: left 0px +Pass background-position: left 1px +Pass background-position: left .1em +Pass background-position: left -0px +Pass background-position: left -1px +Pass background-position: left -.1em +Pass background-position: left top +Pass background-position: left center +Pass background-position: left bottom +Pass background-position: center 5% +Pass background-position: center .5% +Pass background-position: center -5% +Pass background-position: center -.5% +Pass background-position: center 0px +Pass background-position: center 1px +Pass background-position: center .1em +Pass background-position: center -0px +Pass background-position: center -1px +Pass background-position: center -.1em +Pass background-position: center top +Pass background-position: center center +Pass background-position: center bottom +Pass background-position: right 5% +Pass background-position: right .5% +Pass background-position: right -5% +Pass background-position: right -.5% +Pass background-position: right 0px +Pass background-position: right 1px +Pass background-position: right .1em +Pass background-position: right -0px +Pass background-position: right -1px +Pass background-position: right -.1em +Pass background-position: right top +Pass background-position: right center +Pass background-position: right bottom Pass background-position: inherit Pass background-repeat: repeat Pass background-repeat: repeat-x diff --git a/Tests/LibWeb/Text/input/position-serialization.html b/Tests/LibWeb/Text/input/position-serialization.html new file mode 100644 index 0000000000000..9a2f17ac9c025 --- /dev/null +++ b/Tests/LibWeb/Text/input/position-serialization.html @@ -0,0 +1,25 @@ + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html new file mode 100644 index 0000000000000..17060f6607a66 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html @@ -0,0 +1,48 @@ + + + + +CSS Backgrounds and Borders Module Level 3: parsing background-position with valid values + + + + + + + + + + + From 1b4c45b9eb7329a626c35460ed5674819d0b0ddf Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 12 Dec 2024 12:46:14 -0500 Subject: [PATCH 116/237] LibJS: Port test262-runner to use Error and TRY a bit more --- Tests/LibJS/test262-runner.cpp | 38 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/Tests/LibJS/test262-runner.cpp b/Tests/LibJS/test262-runner.cpp index 1579a775fe8a9..0894263641a17 100644 --- a/Tests/LibJS/test262-runner.cpp +++ b/Tests/LibJS/test262-runner.cpp @@ -6,9 +6,9 @@ */ #include +#include #include #include -#include #include #include #include @@ -54,7 +54,7 @@ struct TestError { using ScriptOrModuleProgram = Variant, GC::Ref>; template -static Result parse_program(JS::Realm& realm, StringView source, StringView filepath) +static ErrorOr parse_program(JS::Realm& realm, StringView source, StringView filepath) { auto script_or_error = ScriptType::parse(source, realm, filepath); if (script_or_error.is_error()) { @@ -68,7 +68,7 @@ static Result parse_program(JS::Realm& realm, return ScriptOrModuleProgram { script_or_error.release_value() }; } -static Result parse_program(JS::Realm& realm, StringView source, StringView filepath, JS::Program::Type program_type) +static ErrorOr parse_program(JS::Realm& realm, StringView source, StringView filepath, JS::Program::Type program_type) { if (program_type == JS::Program::Type::Script) return parse_program(realm, source, filepath); @@ -76,7 +76,7 @@ static Result parse_program(JS::Realm& realm, } template -static Result run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program) +static ErrorOr run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program) { auto result = program.visit( [&](auto& visitor) { @@ -115,7 +115,7 @@ static Result run_program(InterpreterT& interpreter, ScriptOrMo static HashMap s_cached_harness_files; -static Result read_harness_file(StringView harness_file) +static ErrorOr read_harness_file(StringView harness_file) { auto cache = s_cached_harness_files.find(harness_file); if (cache == s_cached_harness_files.end()) { @@ -147,7 +147,7 @@ static Result read_harness_file(StringView harness_file) return cache->value.view(); } -static Result, TestError> parse_harness_files(JS::Realm& realm, StringView harness_file) +static ErrorOr, TestError> parse_harness_files(JS::Realm& realm, StringView harness_file) { auto source_or_error = read_harness_file(harness_file); if (source_or_error.is_error()) @@ -193,7 +193,7 @@ struct TestMetadata { StringView type; }; -static Result run_test(StringView source, StringView filepath, TestMetadata const& metadata) +static ErrorOr run_test(StringView source, StringView filepath, TestMetadata const& metadata) { if (s_parse_only || (metadata.is_negative && metadata.phase == NegativePhase::ParseOrEarly && metadata.program_type != JS::Program::Type::Module)) { // Creating the vm and interpreter is heavy so we just parse directly here. @@ -218,6 +218,7 @@ static Result run_test(StringView source, StringView filepath, GC::Ptr realm; GC::Ptr global_object; + auto root_execution_context = MUST(JS::Realm::initialize_host_defined_realm( *vm, [&](JS::Realm& realm_) -> JS::GlobalObject* { @@ -227,17 +228,12 @@ static Result run_test(StringView source, StringView filepath, }, nullptr)); - auto program_or_error = parse_program(*realm, source, filepath, metadata.program_type); - if (program_or_error.is_error()) - return program_or_error.release_error(); - - for (auto& harness_file : metadata.harness_files) { - auto harness_program_or_error = parse_harness_files(*realm, harness_file); - if (harness_program_or_error.is_error()) - return harness_program_or_error.release_error(); - ScriptOrModuleProgram harness_program { harness_program_or_error.release_value() }; - auto result = run_program(vm->bytecode_interpreter(), harness_program); - if (result.is_error()) { + auto program = TRY(parse_program(*realm, source, filepath, metadata.program_type)); + + for (auto harness_file : metadata.harness_files) { + ScriptOrModuleProgram harness_program { TRY(parse_harness_files(*realm, harness_file)) }; + + if (auto result = run_program(vm->bytecode_interpreter(), harness_program); result.is_error()) { return TestError { NegativePhase::Harness, result.error().type, @@ -247,10 +243,10 @@ static Result run_test(StringView source, StringView filepath, } } - return run_program(vm->bytecode_interpreter(), program_or_error.value()); + return run_program(vm->bytecode_interpreter(), program); } -static Result extract_metadata(StringView source) +static ErrorOr extract_metadata(StringView source) { auto lines = source.lines(); @@ -397,7 +393,7 @@ static Result extract_metadata(StringView source) return failed_message; } -static bool verify_test(Result& result, TestMetadata const& metadata, JsonObject& output) +static bool verify_test(ErrorOr& result, TestMetadata const& metadata, JsonObject& output) { if (result.is_error()) { if (result.error().phase == NegativePhase::Harness) { From 40e7f46ac8526ed5ad701bff36f2c5caaff7f75f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 12 Dec 2024 12:55:33 -0500 Subject: [PATCH 117/237] LibJS: Simplify reading test262 harness files Make use of TRY semantics a bit more. And we don't need to store harness files as a ByteString - we can store the contents as the ByteBuffer that we receive from reading the file. --- Tests/LibJS/test262-runner.cpp | 52 ++++++++++++++++------------------ 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/Tests/LibJS/test262-runner.cpp b/Tests/LibJS/test262-runner.cpp index 0894263641a17..7af0445b23491 100644 --- a/Tests/LibJS/test262-runner.cpp +++ b/Tests/LibJS/test262-runner.cpp @@ -113,38 +113,36 @@ static ErrorOr run_program(InterpreterT& interpreter, ScriptOrM return {}; } -static HashMap s_cached_harness_files; - static ErrorOr read_harness_file(StringView harness_file) { - auto cache = s_cached_harness_files.find(harness_file); - if (cache == s_cached_harness_files.end()) { - auto file_or_error = Core::File::open(ByteString::formatted("{}{}", s_harness_file_directory, harness_file), Core::File::OpenMode::Read); - if (file_or_error.is_error()) { - return TestError { - NegativePhase::Harness, - "filesystem", - ByteString::formatted("Could not open file: {}", harness_file), - harness_file - }; - } + static HashMap s_cached_harness_files; - auto contents_or_error = file_or_error.value()->read_until_eof(); - if (contents_or_error.is_error()) { - return TestError { - NegativePhase::Harness, - "filesystem", - ByteString::formatted("Could not read file: {}", harness_file), - harness_file - }; - } + auto cache = [&]() -> ErrorOr { + if (auto it = s_cached_harness_files.find(harness_file); it != s_cached_harness_files.end()) + return StringView { it->value }; + + auto path = ByteString::formatted("{}{}", s_harness_file_directory, harness_file); + auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read)); + + auto contents = TRY(file->read_until_eof()); + s_cached_harness_files.set(harness_file, move(contents)); + + auto it = s_cached_harness_files.find(harness_file); + VERIFY(it != s_cached_harness_files.end()); + + return StringView { it->value }; + }(); - StringView contents_view = contents_or_error.value(); - s_cached_harness_files.set(harness_file, contents_view.to_byte_string()); - cache = s_cached_harness_files.find(harness_file); - VERIFY(cache != s_cached_harness_files.end()); + if (cache.is_error()) { + return TestError { + NegativePhase::Harness, + "filesystem", + ByteString::formatted("Could not read file: {}", harness_file), + harness_file + }; } - return cache->value.view(); + + return cache.value(); } static ErrorOr, TestError> parse_harness_files(JS::Realm& realm, StringView harness_file) From 458af2d2a9903528f643365441993c8a0fefcfdd Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 12 Dec 2024 13:10:03 -0500 Subject: [PATCH 118/237] LibJS: Concatenate all included test262 harness files to a single script For example, for the following `includes` line in a test262 file: includes: [sm/non262-TypedArray-shell.js, sm/non262.js] We currently parse and execute each file in this list as its own script, in the order they appear in the list. Tests have recently been imported test262 from SpiderMonkey which fail with this behavior. In the above example, if the first script references some function from the second script, we will currently fail to execute that harness file. This patch changes our behavior to concatenate all harness files into a single script, which satisfies the behavior required by these new tests. This is how test262.fyi and other test262 runners already behave. --- Tests/LibJS/test262-runner.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Tests/LibJS/test262-runner.cpp b/Tests/LibJS/test262-runner.cpp index 7af0445b23491..a83a565dc492c 100644 --- a/Tests/LibJS/test262-runner.cpp +++ b/Tests/LibJS/test262-runner.cpp @@ -145,20 +145,19 @@ static ErrorOr read_harness_file(StringView harness_file) return cache.value(); } -static ErrorOr, TestError> parse_harness_files(JS::Realm& realm, StringView harness_file) +static ErrorOr, TestError> parse_harness_contents(JS::Realm& realm, StringView harness_contents) { - auto source_or_error = read_harness_file(harness_file); - if (source_or_error.is_error()) - return source_or_error.release_error(); - auto program_or_error = parse_program(realm, source_or_error.value(), harness_file); + auto program_or_error = parse_program(realm, harness_contents, ""sv); + if (program_or_error.is_error()) { return TestError { NegativePhase::Harness, program_or_error.error().type, program_or_error.error().details, - harness_file + ""sv }; } + return program_or_error.release_value().get>(); } @@ -228,15 +227,22 @@ static ErrorOr run_test(StringView source, StringView filepath, auto program = TRY(parse_program(*realm, source, filepath, metadata.program_type)); + StringBuilder harness_builder; + for (auto harness_file : metadata.harness_files) { - ScriptOrModuleProgram harness_program { TRY(parse_harness_files(*realm, harness_file)) }; + auto harness_contents = TRY(read_harness_file(harness_file)); + harness_builder.appendff("{}\n", harness_contents); + } + + if (!harness_builder.is_empty()) { + ScriptOrModuleProgram harness_program { TRY(parse_harness_contents(*realm, harness_builder.string_view())) }; if (auto result = run_program(vm->bytecode_interpreter(), harness_program); result.is_error()) { return TestError { NegativePhase::Harness, result.error().type, result.error().details, - harness_file + ""sv }; } } From f4c03d548e21ffcd3af91ee393b0a50e586be9ce Mon Sep 17 00:00:00 2001 From: rmg-x Date: Fri, 13 Dec 2024 07:37:47 -0600 Subject: [PATCH 119/237] WebContent: Remove unused class "ImageCodecPluginSerenity" --- .../WebContent/ImageCodecPluginSerenity.cpp | 51 ------------------- .../WebContent/ImageCodecPluginSerenity.h | 30 ----------- 2 files changed, 81 deletions(-) delete mode 100644 Services/WebContent/ImageCodecPluginSerenity.cpp delete mode 100644 Services/WebContent/ImageCodecPluginSerenity.h diff --git a/Services/WebContent/ImageCodecPluginSerenity.cpp b/Services/WebContent/ImageCodecPluginSerenity.cpp deleted file mode 100644 index e4c4ff9d42724..0000000000000 --- a/Services/WebContent/ImageCodecPluginSerenity.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022, Dex♪ - * Copyright (c) 2022, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ImageCodecPluginSerenity.h" -#include - -namespace WebContent { - -ImageCodecPluginSerenity::ImageCodecPluginSerenity() = default; -ImageCodecPluginSerenity::~ImageCodecPluginSerenity() = default; - -NonnullRefPtr> ImageCodecPluginSerenity::decode_image(ReadonlyBytes bytes, Function(Web::Platform::DecodedImage&)> on_resolved, Function on_rejected) -{ - if (!m_client) { - m_client = ImageDecoderClient::Client::try_create().release_value_but_fixme_should_propagate_errors(); - m_client->on_death = [&] { - m_client = nullptr; - }; - } - - auto promise = Core::Promise::construct(); - if (on_resolved) - promise->on_resolution = move(on_resolved); - if (on_rejected) - promise->on_rejection = move(on_rejected); - - auto image_decoder_promise = m_client->decode_image( - bytes, - [promise](ImageDecoderClient::DecodedImage& result) -> ErrorOr { - // FIXME: Remove this codec plugin and just use the ImageDecoderClient directly to avoid these copies - Web::Platform::DecodedImage decoded_image; - decoded_image.is_animated = result.is_animated; - decoded_image.loop_count = result.loop_count; - for (auto const& frame : result.frames) { - decoded_image.frames.empend(move(frame.bitmap), frame.duration); - } - promise->resolve(move(decoded_image)); - return {}; - }, - [promise](auto& error) { - promise->reject(Error::copy(error)); - }); - - return promise; -} - -} diff --git a/Services/WebContent/ImageCodecPluginSerenity.h b/Services/WebContent/ImageCodecPluginSerenity.h deleted file mode 100644 index 7f58342e6009f..0000000000000 --- a/Services/WebContent/ImageCodecPluginSerenity.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2022, Dex♪ - * Copyright (c) 2022, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace ImageDecoderClient { -class Client; -} - -namespace WebContent { - -class ImageCodecPluginSerenity final : public Web::Platform::ImageCodecPlugin { -public: - ImageCodecPluginSerenity(); - virtual ~ImageCodecPluginSerenity() override; - - virtual NonnullRefPtr> decode_image(ReadonlyBytes, ESCAPING Function(Web::Platform::DecodedImage&)> on_resolved, ESCAPING Function on_rejected) override; - -private: - RefPtr m_client; -}; - -} From f5e01192cce526a61d09d364222852ac1937bce8 Mon Sep 17 00:00:00 2001 From: Manuel Zahariev Date: Thu, 5 Dec 2024 14:25:35 -0800 Subject: [PATCH 120/237] LibWeb: Layout standalone SVG document with specified dimensions Before, standalone SVG documents were stretched to fit the agent viewport. --- Libraries/LibWeb/Layout/SVGFormattingContext.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index cc7fb562a65e5..66abeec1b8c2d 100644 --- a/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -179,6 +180,18 @@ void SVGFormattingContext::run(AvailableSpace const& available_space) auto& svg_viewport = dynamic_cast(*context_box().dom_node()); auto& svg_box_state = m_state.get_mutable(context_box()); + if (!this->context_box().root().document().is_decoded_svg()) { + // Overwrite the content width/height with the styled node width/height (from ) + + // NOTE: If a height had not been provided by the svg element, it was set to the height of the container + // (see BlockFormattingContext::layout_viewport) + if (svg_box_state.node().computed_values().width().is_length()) + svg_box_state.set_content_width(svg_box_state.node().computed_values().width().length().to_px(svg_box_state.node())); + if (svg_box_state.node().computed_values().height().is_length()) + svg_box_state.set_content_height(svg_box_state.node().computed_values().height().length().to_px(svg_box_state.node())); + // FIXME: In SVG 2, length can also be a percentage. We'll need to support that. + } + // NOTE: We consider all SVG root elements to have definite size in both axes. // I'm not sure if this is good or bad, but our viewport transform logic depends on it. svg_box_state.set_has_definite_width(true); From 5d77104c2f214f0a78d936c014cea21335083eaf Mon Sep 17 00:00:00 2001 From: Manuel Zahariev Date: Thu, 5 Dec 2024 20:41:16 -0800 Subject: [PATCH 121/237] LibWeb: Test layout of standalone SVG document: main use case SVG document with specified width and height attributes is layed out with this width/height. --- Tests/LibWeb/Layout/expected/svg/standalone.txt | 17 +++++++++++++++++ Tests/LibWeb/Layout/input/svg/standalone.svg | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone.txt create mode 100644 Tests/LibWeb/Layout/input/svg/standalone.svg diff --git a/Tests/LibWeb/Layout/expected/svg/standalone.txt b/Tests/LibWeb/Layout/expected/svg/standalone.txt new file mode 100644 index 0000000000000..a7b14e7bf2b22 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone.txt @@ -0,0 +1,17 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 128x256 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,0) content-size 128x256 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,0) content-size 128x256 children: inline + TextNode <#text> + SVGGeometryBox at (0,0) content-size 128x256 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 128x256] + SVGPathPaintable (SVGGeometryBox) [0,0 128x256] + SVGGraphicsPaintable (SVGGraphicsBox) [0,0 128x256] + SVGPathPaintable (SVGGeometryBox) [0,0 128x256] diff --git a/Tests/LibWeb/Layout/input/svg/standalone.svg b/Tests/LibWeb/Layout/input/svg/standalone.svg new file mode 100644 index 0000000000000..463f07a77e3be --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone.svg @@ -0,0 +1,7 @@ + + white diamond on blue + + + + + From 5d85f3a5c8c7fa5631ca5ece3f1ed95c68e480c4 Mon Sep 17 00:00:00 2001 From: Manuel Zahariev Date: Thu, 5 Dec 2024 20:44:44 -0800 Subject: [PATCH 122/237] LibWeb: Test layout of standalone SVG document: edge cases Tests with different combinations of missing width, height and viewBox. All tests confirmed to work on Ladybird: - exactly the same as Chromium (131.0.6778.85) - almost the same as Firefox (129.0.2) - only difference: standalone-w.svg: same size, different alignment --- .../Layout/expected/svg/standalone-h.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-vb-h.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-vb-w.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-vb-wh.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-vb.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-w.txt | 18 ++++++++++++++++++ .../Layout/expected/svg/standalone-wh.txt | 18 ++++++++++++++++++ Tests/LibWeb/Layout/input/svg/standalone-h.svg | 7 +++++++ .../Layout/input/svg/standalone-vb-h.svg | 7 +++++++ .../Layout/input/svg/standalone-vb-w.svg | 7 +++++++ .../Layout/input/svg/standalone-vb-wh.svg | 7 +++++++ .../LibWeb/Layout/input/svg/standalone-vb.svg | 7 +++++++ Tests/LibWeb/Layout/input/svg/standalone-w.svg | 13 +++++++++++++ .../LibWeb/Layout/input/svg/standalone-wh.svg | 7 +++++++ 14 files changed, 181 insertions(+) create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-h.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-vb-h.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-vb-w.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-vb-wh.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-vb.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-w.txt create mode 100644 Tests/LibWeb/Layout/expected/svg/standalone-wh.txt create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-h.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-vb-h.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-vb-w.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-vb-wh.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-vb.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-w.svg create mode 100644 Tests/LibWeb/Layout/input/svg/standalone-wh.svg diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-h.txt b/Tests/LibWeb/Layout/expected/svg/standalone-h.txt new file mode 100644 index 0000000000000..4c1cbf64e8909 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-h.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 128x600 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,172) content-size 128x256 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,172) content-size 128x256 children: inline + TextNode <#text> + SVGGeometryBox at (0,172) content-size 128x256 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 128x600] + SVGPathPaintable (SVGGeometryBox) [0,172 128x256] + SVGGraphicsPaintable (SVGGraphicsBox) [0,172 128x256] + SVGPathPaintable (SVGGeometryBox) [0,172 128x256] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-vb-h.txt b/Tests/LibWeb/Layout/expected/svg/standalone-vb-h.txt new file mode 100644 index 0000000000000..fa98bcbc41749 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-vb-h.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 128x600 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,0) content-size 32x64 children: inline + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 128x600] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + SVGGraphicsPaintable (SVGGraphicsBox) [0,0 32x64] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-vb-w.txt b/Tests/LibWeb/Layout/expected/svg/standalone-vb-w.txt new file mode 100644 index 0000000000000..e5759aadaa5a3 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-vb-w.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 800x256 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,0) content-size 32x64 children: inline + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 800x256] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + SVGGraphicsPaintable (SVGGraphicsBox) [0,0 32x64] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-vb-wh.txt b/Tests/LibWeb/Layout/expected/svg/standalone-vb-wh.txt new file mode 100644 index 0000000000000..d1f3acb87174d --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-vb-wh.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 800x600 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,0) content-size 32x64 children: inline + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 800x600] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + SVGGraphicsPaintable (SVGGraphicsBox) [0,0 32x64] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-vb.txt b/Tests/LibWeb/Layout/expected/svg/standalone-vb.txt new file mode 100644 index 0000000000000..13db94c3ce535 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-vb.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 128x256 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + SVGGraphicsBox at (0,0) content-size 32x64 children: inline + TextNode <#text> + SVGGeometryBox at (0,0) content-size 32x64 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 128x256] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + SVGGraphicsPaintable (SVGGraphicsBox) [0,0 32x64] + SVGPathPaintable (SVGGeometryBox) [0,0 32x64] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-w.txt b/Tests/LibWeb/Layout/expected/svg/standalone-w.txt new file mode 100644 index 0000000000000..d131ba6efba58 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-w.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 800x256 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (336,0) content-size 128x256 children: not-inline + TextNode <#text> + SVGGraphicsBox at (336,0) content-size 128x256 children: inline + TextNode <#text> + SVGGeometryBox at (336,0) content-size 128x256 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 800x256] + SVGPathPaintable (SVGGeometryBox) [336,0 128x256] + SVGGraphicsPaintable (SVGGraphicsBox) [336,0 128x256] + SVGPathPaintable (SVGGeometryBox) [336,0 128x256] + diff --git a/Tests/LibWeb/Layout/expected/svg/standalone-wh.txt b/Tests/LibWeb/Layout/expected/svg/standalone-wh.txt new file mode 100644 index 0000000000000..9748db9258723 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/svg/standalone-wh.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 [BFC] children: not-inline + SVGSVGBox at (0,0) content-size 800x600 [SVG] children: inline + TextNode <#text> + TextNode <#text> + SVGGeometryBox at (250,0) content-size 300x600 children: not-inline + TextNode <#text> + SVGGraphicsBox at (250,0) content-size 300x600 children: inline + TextNode <#text> + SVGGeometryBox at (250,0) content-size 300x600 children: not-inline + TextNode <#text> + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + SVGSVGPaintable (SVGSVGBox) [0,0 800x600] + SVGPathPaintable (SVGGeometryBox) [250,0 300x600] + SVGGraphicsPaintable (SVGGraphicsBox) [250,0 300x600] + SVGPathPaintable (SVGGeometryBox) [250,0 300x600] + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-h.svg b/Tests/LibWeb/Layout/input/svg/standalone-h.svg new file mode 100644 index 0000000000000..33ac71492e2f0 --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-h.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no height + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-vb-h.svg b/Tests/LibWeb/Layout/input/svg/standalone-vb-h.svg new file mode 100644 index 0000000000000..134db81e09191 --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-vb-h.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no viewBox; no height + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-vb-w.svg b/Tests/LibWeb/Layout/input/svg/standalone-vb-w.svg new file mode 100644 index 0000000000000..2418d07a9cebc --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-vb-w.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no viewBox; no width + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-vb-wh.svg b/Tests/LibWeb/Layout/input/svg/standalone-vb-wh.svg new file mode 100644 index 0000000000000..12fbe0626903a --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-vb-wh.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no viewBox; no width; no height + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-vb.svg b/Tests/LibWeb/Layout/input/svg/standalone-vb.svg new file mode 100644 index 0000000000000..f0d8c8639afff --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-vb.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no viewBox + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-w.svg b/Tests/LibWeb/Layout/input/svg/standalone-w.svg new file mode 100644 index 0000000000000..dd260571df492 --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-w.svg @@ -0,0 +1,13 @@ + + + white diamond on blue; no width + + + + + diff --git a/Tests/LibWeb/Layout/input/svg/standalone-wh.svg b/Tests/LibWeb/Layout/input/svg/standalone-wh.svg new file mode 100644 index 0000000000000..6d8d736d62e14 --- /dev/null +++ b/Tests/LibWeb/Layout/input/svg/standalone-wh.svg @@ -0,0 +1,7 @@ + + white diamond on blue; no width ; no height + + + + + From 962441b3cf34c1350877ac7c73351657c10f675b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 13 Dec 2024 09:24:12 -0500 Subject: [PATCH 123/237] LibJS: Avoid internal assertion accessing detached TA internal slots This defers accessing TA internal slots until we know we have a valid, attached TA. Our implementation has assertions that guard against this. --- Libraries/LibJS/Runtime/AtomicsObject.cpp | 15 +++++++++------ .../Atomics/Atomics.compareExchange.js | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Libraries/LibJS/Runtime/AtomicsObject.cpp b/Libraries/LibJS/Runtime/AtomicsObject.cpp index 27574df959d46..f2e8c2ef072c8 100644 --- a/Libraries/LibJS/Runtime/AtomicsObject.cpp +++ b/Libraries/LibJS/Runtime/AtomicsObject.cpp @@ -290,12 +290,6 @@ static ThrowCompletionOr atomic_compare_exchange_impl(VM& vm, TypedArrayB // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, typed_array, index)); - // 2. Let buffer be typedArray.[[ViewedArrayBuffer]]. - auto* buffer = typed_array.viewed_array_buffer(); - - // 3. Let block be buffer.[[ArrayBufferData]]. - auto& block = buffer->buffer(); - Value expected; Value replacement; @@ -319,6 +313,15 @@ static ThrowCompletionOr atomic_compare_exchange_impl(VM& vm, TypedArrayB // 6. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). TRY(revalidate_atomic_access(vm, typed_array, byte_index_in_buffer)); + // NOTE: We defer steps 2 and 3 to ensure we have revalidated the TA before accessing these internal slots. + // In our implementation, accessing [[ArrayBufferData]] on a detached buffer will fail assertions. + + // 2. Let buffer be typedArray.[[ViewedArrayBuffer]]. + auto* buffer = typed_array.viewed_array_buffer(); + + // 3. Let block be buffer.[[ArrayBufferData]]. + auto& block = buffer->buffer(); + // 7. Let elementType be TypedArrayElementType(typedArray). // 8. Let elementSize be TypedArrayElementSize(typedArray). diff --git a/Libraries/LibJS/Tests/builtins/Atomics/Atomics.compareExchange.js b/Libraries/LibJS/Tests/builtins/Atomics/Atomics.compareExchange.js index 60a0e724adff3..ae99428a2dee5 100644 --- a/Libraries/LibJS/Tests/builtins/Atomics/Atomics.compareExchange.js +++ b/Libraries/LibJS/Tests/builtins/Atomics/Atomics.compareExchange.js @@ -21,6 +21,24 @@ test("error cases", () => { const array = new Int32Array(4); Atomics.compareExchange(array, 100, 0, 0); }).toThrow(RangeError); + + expect(() => { + const array = new Int32Array(4); + + function detachArrayWhileAccessingIndex(array) { + return { + valueOf() { + detachArrayBuffer(array.buffer); + return 0; + }, + }; + } + + Atomics.compareExchange(array, detachArrayWhileAccessingIndex(array), 0, 0); + }).toThrowWithMessage( + TypeError, + "TypedArray contains a property which references a value at an index not contained within its buffer's bounds" + ); }); test("basic functionality (non-BigInt)", () => { From 2c3f826162898bea9040308218c5438c79e473d6 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 13 Dec 2024 14:49:07 +0000 Subject: [PATCH 124/237] Tests: Disable crashing css-hover-shadow-dom.html test --- Tests/LibWeb/TestConfig.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini index 6a6d686a28328..bd91d98dc672a 100644 --- a/Tests/LibWeb/TestConfig.ini +++ b/Tests/LibWeb/TestConfig.ini @@ -169,3 +169,7 @@ Text/input/wpt-import/html/rendering/replaced-elements/svg-embedded-sizing/svg-i Text/input/wpt-import/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-object-auto.html Text/input/wpt-import/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-object-fixed.html Text/input/wpt-import/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-object-percentage.html + +; Crashes inconsistently on CI +; https://github.com/LadybirdBrowser/ladybird/issues/2900 +Text/input/ShadowDOM/css-hover-shadow-dom.html From e318316af7af6bbcb91ce411cff3e47678a6831e Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 13 Dec 2024 13:51:45 +0000 Subject: [PATCH 125/237] LibWeb/CSS: Add missing parameter to CSSStyleValue::to_string() calls --- Libraries/LibWeb/CSS/Parser/RuleParsing.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index 95d1d62267b42..e27ca6a299082 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -667,7 +667,7 @@ GC::Ptr Parser::convert_to_font_face_rule(AtRule const& rule) if (value.has_value()) { font_display = *value; } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: `{}` is not a valid value for font-display", keyword_value->to_string(CSSStyleValue::SerializationMode::Normal)); } } } @@ -746,7 +746,7 @@ GC::Ptr Parser::convert_to_font_face_rule(AtRule const& rule) } font_feature_settings = move(settings); } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-feature-settings descriptor, not compatible with value returned from parsing font-feature-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal)); } } return; @@ -820,7 +820,7 @@ GC::Ptr Parser::convert_to_font_face_rule(AtRule const& rule) } font_variation_settings = move(settings); } else { - dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string()); + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Failed to parse font-variation-settings descriptor, not compatible with value returned from parsing font-variation-settings property: {}", value.value()->to_string(CSSStyleValue::SerializationMode::Normal)); } } return; From c3aa8af51408c46a8f36467b3d850ad8b9981080 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 15:24:26 +0100 Subject: [PATCH 126/237] LibCrypto: Define SECP521r1 Define SECP521r1 with its constants. Since the parameters cannot be represented as full bytes, a slight modification has been added to the byte size. The current implementation of SECPxxxr1 does not work with this curve. --- Libraries/LibCrypto/Curves/SECPxxxr1.h | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.h b/Libraries/LibCrypto/Curves/SECPxxxr1.h index a2dfc583470a9..919bb83147b6a 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.h +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.h @@ -99,7 +99,7 @@ class SECPxxxr1 : public EllipticCurve { // Curve parameters static constexpr size_t KEY_BIT_SIZE = bit_size; - static constexpr size_t KEY_BYTE_SIZE = KEY_BIT_SIZE / 8; + static constexpr size_t KEY_BYTE_SIZE = ceil_div(KEY_BIT_SIZE, 8ull); static constexpr size_t POINT_BYTE_SIZE = 1 + 2 * KEY_BYTE_SIZE; static constexpr StorageType make_unsigned_fixed_big_int_from_string(StringView str) @@ -216,7 +216,7 @@ class SECPxxxr1 : public EllipticCurve { ErrorOr generate_public_key_point(UnsignedBigInteger scalar) { - VERIFY(scalar.byte_length() == KEY_BYTE_SIZE); + VERIFY(scalar.byte_length() >= KEY_BYTE_SIZE); return compute_coordinate_point(scalar, SECPxxxr1Point { UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1, KEY_BYTE_SIZE), UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1 + KEY_BYTE_SIZE, KEY_BYTE_SIZE) }); } @@ -399,10 +399,11 @@ class SECPxxxr1 : public EllipticCurve { private: StorageType unsigned_big_integer_to_storage_type(UnsignedBigInteger big) { - VERIFY(big.length() >= KEY_BIT_SIZE / 32); + constexpr size_t word_count = (KEY_BYTE_SIZE + 4 - 1) / 4; + VERIFY(big.length() >= word_count); StorageType val = 0u; - for (size_t i = 0; i < (KEY_BIT_SIZE / 32); i++) { + for (size_t i = 0; i < word_count; i++) { StorageType rr = big.words()[i]; val |= (rr << (i * 32)); } @@ -411,8 +412,9 @@ class SECPxxxr1 : public EllipticCurve { UnsignedBigInteger storage_type_to_unsigned_big_integer(StorageType val) { - Vector words; - for (size_t i = 0; i < (KEY_BIT_SIZE / 32); i++) { + constexpr size_t word_count = (KEY_BYTE_SIZE + 4 - 1) / 4; + Vector words; + for (size_t i = 0; i < word_count; i++) { words.append(static_cast((val >> (i * 32)) & 0xFFFFFFFF)); } return UnsignedBigInteger(move(words)); @@ -851,4 +853,14 @@ static constexpr SECPxxxr1CurveParameters SECP384r1_CURVE_PARAMETERS { }; using SECP384r1 = SECPxxxr1<384, SECP384r1_CURVE_PARAMETERS>; +// SECP521r1 curve +static constexpr SECPxxxr1CurveParameters SECP521r1_CURVE_PARAMETERS { + .prime = "01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF"sv, + .a = "01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFC"sv, + .b = "0051_953EB961_8E1C9A1F_929A21A0_B68540EE_A2DA725B_99B315F3_B8B48991_8EF109E1_56193951_EC7E937B_1652C0BD_3BB1BF07_3573DF88_3D2C34F1_EF451FD4_6B503F00"sv, + .order = "01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFA_51868783_BF2F966B_7FCC0148_F709A5D0_3BB5C9B8_899C47AE_BB6FB71E_91386409"sv, + .generator_point = "04_00C6_858E06B7_0404E9CD_9E3ECB66_2395B442_9C648139_053FB521_F828AF60_6B4D3DBA_A14B5E77_EFE75928_FE1DC127_A2FFA8DE_3348B3C1_856A429B_F97E7E31_C2E5BD66_0118_39296A78_9A3BC004_5C8A5FB4_2C7D1BD9_98F54449_579B4468_17AFBD17_273E662C_97EE7299_5EF42640_C550B901_3FAD0761_353C7086_A272C240_88BE9476_9FD16650"sv, +}; +using SECP521r1 = SECPxxxr1<521, SECP521r1_CURVE_PARAMETERS>; + } From 70db7772b8973acc700540914482e2c97263e4e4 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 15:28:33 +0100 Subject: [PATCH 127/237] LibWeb: Expose support for P-521 in ECDH and ECDSA Replace all TODOs and FIXMEs requiring P-521 support with actual code paths that make use of it. Gets a few tests by simply not failing early. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 97 +++++++++---------- .../derive_bits_keys/ecdh_bits.https.any.txt | 6 +- .../sign_verify/ecdsa.https.any.txt | 28 +++--- 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index a476ba7140a20..f47c7aa6336c3 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -3,6 +3,7 @@ * Copyright (c) 2024, stelar7 * Copyright (c) 2024, Jelle Raaijmakers * Copyright (c) 2024, Andreas Kling + * Copyright (c) 2024, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -2265,17 +2266,16 @@ WebIDL::ExceptionOr, GC::Ref>> ECDSA:: // 2. If the namedCurve member of normalizedAlgorithm is "P-256", "P-384" or "P-521": // Generate an Elliptic Curve key pair, as defined in [RFC6090] // with domain parameters for the curve identified by the namedCurve member of normalizedAlgorithm. - Variant curve; + Variant curve; if (normalized_algorithm.named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { if (normalized_algorithm.named_curve == "P-256") curve = ::Crypto::Curves::SECP256r1 {}; - - if (normalized_algorithm.named_curve == "P-384") + else if (normalized_algorithm.named_curve == "P-384") curve = ::Crypto::Curves::SECP384r1 {}; - - // FIXME: Support P-521 - if (normalized_algorithm.named_curve == "P-521") - return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); + else if (normalized_algorithm.named_curve == "P-521") + curve = ::Crypto::Curves::SECP521r1 {}; + else + VERIFY_NOT_REACHED(); } else { // If the namedCurve member of normalizedAlgorithm is a value specified in an applicable specification: // Perform the ECDSA generation steps specified in that specification, @@ -2399,17 +2399,16 @@ WebIDL::ExceptionOr> ECDSA::sign(AlgorithmParams const& // 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521": if (named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { size_t coord_size; - Variant curve; + Variant curve; if (named_curve == "P-256") { - coord_size = 32; + coord_size = 256 / 8; curve = ::Crypto::Curves::SECP256r1 {}; } else if (named_curve == "P-384") { - coord_size = 48; + coord_size = 384 / 8; curve = ::Crypto::Curves::SECP384r1 {}; } else if (named_curve == "P-521") { - // FIXME: Support P-521 - coord_size = 66; - return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); + coord_size = ceil_div(521, 8); + curve = ::Crypto::Curves::SECP521r1 {}; } else { VERIFY_NOT_REACHED(); } @@ -2492,17 +2491,16 @@ WebIDL::ExceptionOr ECDSA::verify(AlgorithmParams const& params, GC:: auto result = false; - Variant curve; + Variant curve; if (named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { if (named_curve == "P-256") curve = ::Crypto::Curves::SECP256r1 {}; - - if (named_curve == "P-384") + else if (named_curve == "P-384") curve = ::Crypto::Curves::SECP384r1 {}; - - // FIXME: Support P-521 - if (named_curve == "P-521") - return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); + else if (named_curve == "P-521") + curve = ::Crypto::Curves::SECP521r1 {}; + else + VERIFY_NOT_REACHED(); // Perform the ECDSA verifying process, as specified in [RFC6090], Section 5.3, // with M as the received message, signature as the received signature @@ -2810,11 +2808,11 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& size_t coord_size; if (named_curve == "P-256"sv) - coord_size = 32; + coord_size = 256 / 8; else if (named_curve == "P-384"sv) - coord_size = 48; + coord_size = 384 / 8; else if (named_curve == "P-521"sv) - coord_size = 66; + coord_size = ceil_div(521, 8); else VERIFY_NOT_REACHED(); @@ -3145,16 +3143,16 @@ WebIDL::ExceptionOr> ECDSA::export_key(Bindings::KeyFormat f }, [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { size_t coord_size; - Variant curve; + Variant curve; if (algorithm.named_curve() == "P-256"sv) { curve = ::Crypto::Curves::SECP256r1 {}; - coord_size = 256 / 8; + coord_size = 32; } else if (algorithm.named_curve() == "P-384"sv) { curve = ::Crypto::Curves::SECP384r1 {}; - coord_size = 384 / 8; + coord_size = 48; } else if (algorithm.named_curve() == "P-521"sv) { - // FIXME: Support P-521 - return {}; + curve = ::Crypto::Curves::SECP521r1 {}; + coord_size = 66; } else { VERIFY_NOT_REACHED(); } @@ -3284,17 +3282,16 @@ WebIDL::ExceptionOr, GC::Ref>> ECDH::g // 2. If the namedCurve member of normalizedAlgorithm is "P-256", "P-384" or "P-521": // Generate an Elliptic Curve key pair, as defined in [RFC6090] // with domain parameters for the curve identified by the namedCurve member of normalizedAlgorithm. - Variant curve; + Variant curve; if (normalized_algorithm.named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { if (normalized_algorithm.named_curve == "P-256") curve = ::Crypto::Curves::SECP256r1 {}; - - if (normalized_algorithm.named_curve == "P-384") + else if (normalized_algorithm.named_curve == "P-384") curve = ::Crypto::Curves::SECP384r1 {}; - - // FIXME: Support P-521 - if (normalized_algorithm.named_curve == "P-521") - return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); + else if (normalized_algorithm.named_curve == "P-521") + curve = ::Crypto::Curves::SECP521r1 {}; + else + VERIFY_NOT_REACHED(); } else { // If the namedCurve member of normalizedAlgorithm is a value specified in an applicable specification // that specifies the use of that value with ECDH: @@ -3415,15 +3412,15 @@ WebIDL::ExceptionOr> ECDH::derive_bits(AlgorithmParams auto private_key_data = key->handle().get<::Crypto::PK::ECPrivateKey<>>(); auto public_key_data = public_key->handle().get<::Crypto::PK::ECPublicKey<>>(); - Variant curve; - if (internal_algorithm.named_curve() == "P-256"sv) { + Variant curve; + if (internal_algorithm.named_curve() == "P-256"sv) curve = ::Crypto::Curves::SECP256r1 {}; - } else if (internal_algorithm.named_curve() == "P-384"sv) { + else if (internal_algorithm.named_curve() == "P-384"sv) curve = ::Crypto::Curves::SECP384r1 {}; - } else if (internal_algorithm.named_curve() == "P-521"sv) { - // TODO: Support P-521 - return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string); - } + else if (internal_algorithm.named_curve() == "P-521"sv) + curve = ::Crypto::Curves::SECP521r1 {}; + else + VERIFY_NOT_REACHED(); auto maybe_secret = curve.visit( [](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Point> { return Error::from_string_literal("noop error"); }, @@ -3713,11 +3710,11 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& if (named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) { size_t coord_size; if (named_curve == "P-256"sv) - coord_size = 32; + coord_size = 256 / 8; else if (named_curve == "P-384"sv) - coord_size = 48; + coord_size = 384 / 8; else if (named_curve == "P-521"sv) - coord_size = 66; + coord_size = ceil_div(521, 8); else VERIFY_NOT_REACHED(); @@ -4039,16 +4036,16 @@ WebIDL::ExceptionOr> ECDH::export_key(Bindings::KeyFormat fo }, [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { size_t coord_size; - Variant curve; + Variant curve; if (algorithm.named_curve() == "P-256"sv) { curve = ::Crypto::Curves::SECP256r1 {}; - coord_size = 256 / 8; + coord_size = 32; } else if (algorithm.named_curve() == "P-384"sv) { curve = ::Crypto::Curves::SECP384r1 {}; - coord_size = 384 / 8; + coord_size = 48; } else if (algorithm.named_curve() == "P-521"sv) { - // FIXME: Support P-521 - return {}; + curve = ::Crypto::Curves::SECP521r1 {}; + coord_size = 66; } else { VERIFY_NOT_REACHED(); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.txt index 2c4c5732ee0d5..b291b3b2ce361 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.txt @@ -2,8 +2,8 @@ Harness status: OK Found 40 tests -30 Pass -10 Fail +31 Pass +9 Fail Pass setup - define tests Fail P-521 good parameters Fail P-521 mixed case parameters @@ -17,7 +17,7 @@ Pass P-521 no deriveBits usage for base key Pass P-521 base key is not a private key Pass P-521 public property value is a private key Pass P-521 public property value is a secret key -Fail P-521 asking for too many bits +Pass P-521 asking for too many bits Pass P-256 good parameters Pass P-256 mixed case parameters Pass P-256 short result diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/sign_verify/ecdsa.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/sign_verify/ecdsa.https.any.txt index c01fdac6a98da..9461ed38b4594 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/sign_verify/ecdsa.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/sign_verify/ecdsa.https.any.txt @@ -2,8 +2,8 @@ Harness status: OK Found 253 tests -193 Pass -60 Fail +205 Pass +48 Fail Pass setup Pass ECDSA P-256 with SHA-1 verification Pass ECDSA P-256 with SHA-256 verification @@ -233,27 +233,27 @@ Fail ECDSA P-521 with SHA-1 - The signature was truncated by 1 byte verification Fail ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-256 verification Fail ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-384 verification Fail ECDSA P-521 with SHA-1 - The signature was made using SHA-1, however verification is being done using SHA-512 verification -Fail ECDSA P-521 with SHA-1 - Signature has excess padding verification -Fail ECDSA P-521 with SHA-1 - The signature is empty verification -Fail ECDSA P-521 with SHA-1 - The signature is all zeroes verification +Pass ECDSA P-521 with SHA-1 - Signature has excess padding verification +Pass ECDSA P-521 with SHA-1 - The signature is empty verification +Pass ECDSA P-521 with SHA-1 - The signature is all zeroes verification Fail ECDSA P-521 with SHA-256 - The signature was truncated by 1 byte verification Fail ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-1 verification Fail ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-384 verification Fail ECDSA P-521 with SHA-256 - The signature was made using SHA-256, however verification is being done using SHA-512 verification -Fail ECDSA P-521 with SHA-256 - Signature has excess padding verification -Fail ECDSA P-521 with SHA-256 - The signature is empty verification -Fail ECDSA P-521 with SHA-256 - The signature is all zeroes verification +Pass ECDSA P-521 with SHA-256 - Signature has excess padding verification +Pass ECDSA P-521 with SHA-256 - The signature is empty verification +Pass ECDSA P-521 with SHA-256 - The signature is all zeroes verification Fail ECDSA P-521 with SHA-384 - The signature was truncated by 1 byte verification Fail ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-1 verification Fail ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-256 verification Fail ECDSA P-521 with SHA-384 - The signature was made using SHA-384, however verification is being done using SHA-512 verification -Fail ECDSA P-521 with SHA-384 - Signature has excess padding verification -Fail ECDSA P-521 with SHA-384 - The signature is empty verification -Fail ECDSA P-521 with SHA-384 - The signature is all zeroes verification +Pass ECDSA P-521 with SHA-384 - Signature has excess padding verification +Pass ECDSA P-521 with SHA-384 - The signature is empty verification +Pass ECDSA P-521 with SHA-384 - The signature is all zeroes verification Fail ECDSA P-521 with SHA-512 - The signature was truncated by 1 byte verification Fail ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-1 verification Fail ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-256 verification Fail ECDSA P-521 with SHA-512 - The signature was made using SHA-512, however verification is being done using SHA-384 verification -Fail ECDSA P-521 with SHA-512 - Signature has excess padding verification -Fail ECDSA P-521 with SHA-512 - The signature is empty verification -Fail ECDSA P-521 with SHA-512 - The signature is all zeroes verification \ No newline at end of file +Pass ECDSA P-521 with SHA-512 - Signature has excess padding verification +Pass ECDSA P-521 with SHA-512 - The signature is empty verification +Pass ECDSA P-521 with SHA-512 - The signature is all zeroes verification \ No newline at end of file From 9240d382736c0c6583ad399e5fd49c989034185d Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 15:37:34 +0100 Subject: [PATCH 128/237] LibCrypto+LibTLS+LibWeb: Store EC key size + refactor serialization In order for public/private key serialization to work correctly we must store the size of the key because P-521 cannot be stored as full words inside `UnsignedBigInteger` and therefore is exported as the wrong length (68 instead of 66). This makes it also possible to refactor some methods and cleanup constants scattered around. Gets almost all import/export tests, expect the JWK ones that calculate the public key on export. The `SECPxxxr1` implementation currently fails to do calculations for P-521. --- .../LibCrypto/Certificate/Certificate.cpp | 5 -- Libraries/LibCrypto/Curves/SECPxxxr1.h | 50 +++++++++-- Libraries/LibCrypto/PK/EC.cpp | 15 ++-- Libraries/LibCrypto/PK/EC.h | 47 ++++++++-- Libraries/LibCrypto/PK/RSA.cpp | 5 -- Libraries/LibTLS/HandshakeServer.cpp | 3 +- Libraries/LibTLS/TLSv12.cpp | 3 +- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 88 ++++++++----------- .../import_export/ec_importKey.https.any.txt | 40 ++++----- 9 files changed, 146 insertions(+), 110 deletions(-) diff --git a/Libraries/LibCrypto/Certificate/Certificate.cpp b/Libraries/LibCrypto/Certificate/Certificate.cpp index 6750fae23a106..ef9f432ecf816 100644 --- a/Libraries/LibCrypto/Certificate/Certificate.cpp +++ b/Libraries/LibCrypto/Certificate/Certificate.cpp @@ -14,11 +14,6 @@ #include #include -namespace { -// Used by ASN1 macros -static String s_error_string; -} - namespace Crypto::Certificate { static ErrorOr parse_certificate_version(Crypto::ASN1::Decoder& decoder, Vector current_scope) diff --git a/Libraries/LibCrypto/Curves/SECPxxxr1.h b/Libraries/LibCrypto/Curves/SECPxxxr1.h index 919bb83147b6a..a8c66d875156f 100644 --- a/Libraries/LibCrypto/Curves/SECPxxxr1.h +++ b/Libraries/LibCrypto/Curves/SECPxxxr1.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, Michiel Visser + * Copyright (c) 2024, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -36,6 +37,22 @@ struct SECPxxxr1CurveParameters { struct SECPxxxr1Point { UnsignedBigInteger x; UnsignedBigInteger y; + size_t size; + + static ErrorOr scalar_to_bytes(UnsignedBigInteger const& a, size_t size) + { + auto a_bytes = TRY(ByteBuffer::create_uninitialized(a.byte_length())); + auto a_size = a.export_data(a_bytes.span()); + VERIFY(a_size >= size); + + for (size_t i = 0; i < a_size - size; i++) { + if (a_bytes[i] != 0) { + return Error::from_string_literal("Scalar is too large for the given size"); + } + } + + return a_bytes.slice(a_size - size, size); + } static ErrorOr from_uncompressed(ReadonlyBytes data) { @@ -46,16 +63,30 @@ struct SECPxxxr1Point { return SECPxxxr1Point { UnsignedBigInteger::import_data(data.slice(1, half_size)), UnsignedBigInteger::import_data(data.slice(1 + half_size, half_size)), + half_size, }; } + ErrorOr x_bytes() const + { + return scalar_to_bytes(x, size); + } + + ErrorOr y_bytes() const + { + return scalar_to_bytes(y, size); + } + ErrorOr to_uncompressed() const { - auto bytes = TRY(ByteBuffer::create_uninitialized(1 + x.byte_length() + y.byte_length())); + auto x = TRY(x_bytes()); + auto y = TRY(y_bytes()); + + auto bytes = TRY(ByteBuffer::create_uninitialized(1 + (size * 2))); bytes[0] = 0x04; // uncompressed - auto x_size = x.export_data(bytes.span().slice(1)); - auto y_size = y.export_data(bytes.span().slice(1 + x_size)); - return bytes.slice(0, 1 + x_size + y_size); + bytes.overwrite(1, x.data(), size); + bytes.overwrite(1 + size, y.data(), size); + return bytes; } }; @@ -218,7 +249,11 @@ class SECPxxxr1 : public EllipticCurve { { VERIFY(scalar.byte_length() >= KEY_BYTE_SIZE); - return compute_coordinate_point(scalar, SECPxxxr1Point { UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1, KEY_BYTE_SIZE), UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1 + KEY_BYTE_SIZE, KEY_BYTE_SIZE) }); + return compute_coordinate_point(scalar, SECPxxxr1Point { + UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1, KEY_BYTE_SIZE), + UnsignedBigInteger::import_data(GENERATOR_POINT.data() + 1 + KEY_BYTE_SIZE, KEY_BYTE_SIZE), + KEY_BYTE_SIZE, + }); } ErrorOr compute_coordinate(ReadonlyBytes scalar_bytes, ReadonlyBytes point_bytes) override @@ -248,8 +283,9 @@ class SECPxxxr1 : public EllipticCurve { auto result_point = TRY(compute_coordinate_internal(scalar_int, JacobianPoint { point_x_int, point_y_int, 1u })); return SECPxxxr1Point { - .x = storage_type_to_unsigned_big_integer(result_point.x), - .y = storage_type_to_unsigned_big_integer(result_point.y), + storage_type_to_unsigned_big_integer(result_point.x), + storage_type_to_unsigned_big_integer(result_point.y), + KEY_BYTE_SIZE, }; } diff --git a/Libraries/LibCrypto/PK/EC.cpp b/Libraries/LibCrypto/PK/EC.cpp index 3aed0f32871d7..11d720f73b787 100644 --- a/Libraries/LibCrypto/PK/EC.cpp +++ b/Libraries/LibCrypto/PK/EC.cpp @@ -9,11 +9,6 @@ #include #include -namespace { -// Used by ASN1 macros -static String s_error_string; -} - namespace Crypto::PK { template<> @@ -23,9 +18,8 @@ ErrorOr ECPrivateKey::export_as_der() const TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { TRY(encoder.write(1u)); // version - auto d_bytes = TRY(ByteBuffer::create_uninitialized(m_d.byte_length())); - auto d_size = m_d.export_data(d_bytes.span()); - TRY(encoder.write(d_bytes.span().slice(0, d_size))); + auto d = TRY(d_bytes()); + TRY(encoder.write(d)); if (m_parameters.has_value()) { TRY(encoder.write_constructed(ASN1::Class::Context, static_cast(0), [&]() -> ErrorOr { @@ -65,6 +59,7 @@ static ErrorOr> read_ec_public_key(ReadonlyBytes bytes, Vector { UnsignedBigInteger::import_data(bytes.slice(1, half_size)), UnsignedBigInteger::import_data(bytes.slice(1 + half_size, half_size)), + half_size, }; } else { ERROR_WITH_SCOPE("Unsupported public key format"); @@ -125,7 +120,7 @@ ErrorOr EC::parse_ec_key(ReadonlyBytes der, bool is_private, Ve keypair.public_key = maybe_public_key.release_value(); public_key = keypair.public_key; - if (keypair.public_key.x().byte_length() != private_key.byte_length() || keypair.public_key.y().byte_length() != private_key.byte_length()) { + if (keypair.public_key.scalar_size() != private_key_bytes.length()) { ERROR_WITH_SCOPE("Invalid public key length"); } @@ -133,7 +128,7 @@ ErrorOr EC::parse_ec_key(ReadonlyBytes der, bool is_private, Ve } } - keypair.private_key = ECPrivateKey { private_key, parameters, public_key }; + keypair.private_key = ECPrivateKey { private_key, private_key_bytes.length(), parameters, public_key }; EXIT_SCOPE(); return keypair; diff --git a/Libraries/LibCrypto/PK/EC.h b/Libraries/LibCrypto/PK/EC.h index 9f1195f260e5f..5ed61147d98c9 100644 --- a/Libraries/LibCrypto/PK/EC.h +++ b/Libraries/LibCrypto/PK/EC.h @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace Crypto::PK { @@ -16,41 +17,62 @@ namespace Crypto::PK { template class ECPublicKey { public: - ECPublicKey(Integer x, Integer y) + ECPublicKey(Integer x, Integer y, size_t scalar_size) : m_x(move(x)) , m_y(move(y)) + , m_scalar_size(scalar_size) + { + } + + ECPublicKey(Curves::SECPxxxr1Point point) + : m_x(move(point.x)) + , m_y(move(point.y)) + , m_scalar_size(point.size) { } ECPublicKey() : m_x(0) , m_y(0) + , m_scalar_size(0) + { + } + + size_t scalar_size() const { return m_scalar_size; } + + ErrorOr x_bytes() const + { + return Curves::SECPxxxr1Point::scalar_to_bytes(m_x, m_scalar_size); + } + + ErrorOr y_bytes() const { + return Curves::SECPxxxr1Point::scalar_to_bytes(m_y, m_scalar_size); } - Integer const& x() const { return m_x; } - Integer const& y() const { return m_y; } + Curves::SECPxxxr1Point to_secpxxxr1_point() const + { + return Curves::SECPxxxr1Point { m_x, m_y, m_scalar_size }; + } ErrorOr to_uncompressed() const { - auto bytes = TRY(ByteBuffer::create_uninitialized(1 + m_x.byte_length() + m_y.byte_length())); - bytes[0] = 0x04; // uncompressed - auto x_size = m_x.export_data(bytes.span().slice(1)); - auto y_size = m_y.export_data(bytes.span().slice(1 + x_size)); - return bytes.slice(0, 1 + x_size + y_size); + return to_secpxxxr1_point().to_uncompressed(); } private: Integer m_x; Integer m_y; + size_t m_scalar_size; }; // https://www.rfc-editor.org/rfc/rfc5915#section-3 template class ECPrivateKey { public: - ECPrivateKey(Integer d, Optional> parameters, Optional> public_key) + ECPrivateKey(Integer d, size_t scalar_size, Optional> parameters, Optional> public_key) : m_d(move(d)) + , m_scalar_size(scalar_size) , m_parameters(parameters) , m_public_key(public_key) { @@ -59,6 +81,11 @@ class ECPrivateKey { ECPrivateKey() = default; Integer const& d() const { return m_d; } + ErrorOr d_bytes() const + { + return Curves::SECPxxxr1Point::scalar_to_bytes(m_d, m_scalar_size); + } + Optional const&> parameters() const { return m_parameters; } Optional const&> public_key() const { return m_public_key; } @@ -66,6 +93,8 @@ class ECPrivateKey { private: Integer m_d; + size_t m_scalar_size; + Optional> m_parameters; Optional> m_public_key; }; diff --git a/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp index 58a13ed010b77..ebba1ebca7065 100644 --- a/Libraries/LibCrypto/PK/RSA.cpp +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -13,11 +13,6 @@ #include #include -namespace { -// Used by ASN1 macros -static String s_error_string; -} - namespace Crypto::PK { ErrorOr RSA::parse_rsa_key(ReadonlyBytes der, bool is_private, Vector current_scope) diff --git a/Libraries/LibTLS/HandshakeServer.cpp b/Libraries/LibTLS/HandshakeServer.cpp index c1c72f303521b..a84806c24ef7a 100644 --- a/Libraries/LibTLS/HandshakeServer.cpp +++ b/Libraries/LibTLS/HandshakeServer.cpp @@ -458,8 +458,7 @@ ssize_t TLSv12::verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_b dbgln("verify_ecdsa_server_key_exchange failed: Attempting to verify signature without certificates"); return (i8)Error::NotSafe; } - auto server_public_key = m_context.certificates.first().public_key.ec; - auto server_point = Crypto::Curves::SECPxxxr1Point { server_public_key.x(), server_public_key.y() }; + auto server_point = m_context.certificates.first().public_key.ec.to_secpxxxr1_point(); auto message_result = ByteBuffer::create_uninitialized(64 + server_key_info_buffer.size()); if (message_result.is_error()) { diff --git a/Libraries/LibTLS/TLSv12.cpp b/Libraries/LibTLS/TLSv12.cpp index 77da37e30058a..34682150ef67e 100644 --- a/Libraries/LibTLS/TLSv12.cpp +++ b/Libraries/LibTLS/TLSv12.cpp @@ -371,8 +371,7 @@ bool Context::verify_certificate_pair(Certificate const& subject, Certificate co return false; } - auto public_key = issuer.public_key.ec; - auto public_point = Crypto::Curves::SECPxxxr1Point { public_key.x(), public_key.y() }; + auto public_point = issuer.public_key.ec.to_secpxxxr1_point(); auto maybe_signature = Crypto::Curves::SECPxxxr1Signature::from_asn(subject.signature_value, {}); if (maybe_signature.is_error()) { diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index f47c7aa6336c3..a4d588d69415f 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -2304,7 +2304,7 @@ WebIDL::ExceptionOr, GC::Ref>> ECDSA:: return WebIDL::OperationError::create(m_realm, "Failed to create valid crypto instance"_string); auto public_key_data = maybe_public_key_data.release_value(); - auto ec_public_key = ::Crypto::PK::ECPublicKey<> { public_key_data.x, public_key_data.y }; + auto ec_public_key = ::Crypto::PK::ECPublicKey<> { public_key_data }; // 7. Let algorithm be a new EcKeyAlgorithm object. auto algorithm = EcKeyAlgorithm::create(m_realm); @@ -2331,7 +2331,7 @@ WebIDL::ExceptionOr, GC::Ref>> ECDSA:: public_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Verify } })); // 15. Let privateKey be a new CryptoKey representing the private key of the generated key pair. - auto ec_private_key = ::Crypto::PK::ECPrivateKey<> { private_key_data, {}, ec_public_key }; + auto ec_private_key = ::Crypto::PK::ECPrivateKey<> { private_key_data, public_key_data.size, {}, ec_public_key }; auto private_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { ec_private_key }); // 16. Set the [[type]] internal slot of privateKey to "private" @@ -2512,7 +2512,7 @@ WebIDL::ExceptionOr ECDSA::verify(AlgorithmParams const& params, GC:: auto maybe_result = curve.visit( [](Empty const&) -> ErrorOr { return Error::from_string_literal("Failed to create valid crypto instance"); }, - [&](auto instance) { return instance.verify_point(M, ::Crypto::Curves::SECPxxxr1Point { Q.x(), Q.y() }, ::Crypto::Curves::SECPxxxr1Signature { r, s }); }); + [&](auto instance) { return instance.verify_point(M, Q.to_secpxxxr1_point(), ::Crypto::Curves::SECPxxxr1Signature { r, s }); }); if (maybe_result.is_error()) { auto error_message = MUST(String::from_utf8(maybe_result.error().string_literal())); @@ -2839,6 +2839,7 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& auto public_key = ::Crypto::PK::ECPublicKey<> { ::Crypto::UnsignedBigInteger::import_data(x_bytes), ::Crypto::UnsignedBigInteger::import_data(y_bytes), + coord_size, }; // If the d field is present: @@ -2856,6 +2857,7 @@ WebIDL::ExceptionOr> ECDSA::import_key(AlgorithmParams const& // by interpreting jwk according to Section 6.2.2 of JSON Web Algorithms [JWA]. auto private_key = ::Crypto::PK::ECPrivateKey<> { ::Crypto::UnsignedBigInteger::import_data(d_bytes), + coord_size, {}, public_key, }; @@ -3130,46 +3132,39 @@ WebIDL::ExceptionOr> ECDSA::export_key(Bindings::KeyFormat f auto maybe_error = handle.visit( [&](::Crypto::PK::ECPublicKey<> const& public_key) -> ErrorOr { // 2. Set the x attribute of jwk according to the definition in Section 6.2.1.2 of JSON Web Algorithms [JWA]. - auto x_bytes = TRY(ByteBuffer::create_uninitialized(public_key.x().byte_length())); - auto x_size = public_key.x().export_data(x_bytes); - jwk.x = TRY(encode_base64url(x_bytes.span().slice(0, x_size), AK::OmitPadding::Yes)); + auto x_bytes = TRY(public_key.x_bytes()); + jwk.x = TRY(encode_base64url(x_bytes, AK::OmitPadding::Yes)); // 3. Set the y attribute of jwk according to the definition in Section 6.2.1.3 of JSON Web Algorithms [JWA]. - auto y_bytes = TRY(ByteBuffer::create_uninitialized(public_key.y().byte_length())); - auto y_size = public_key.y().export_data(y_bytes); - jwk.y = TRY(encode_base64url(y_bytes.span().slice(0, y_size), AK::OmitPadding::Yes)); + auto y_bytes = TRY(public_key.y_bytes()); + jwk.y = TRY(encode_base64url(y_bytes, AK::OmitPadding::Yes)); return {}; }, [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { - size_t coord_size; Variant curve; - if (algorithm.named_curve() == "P-256"sv) { + if (algorithm.named_curve() == "P-256"sv) curve = ::Crypto::Curves::SECP256r1 {}; - coord_size = 32; - } else if (algorithm.named_curve() == "P-384"sv) { + else if (algorithm.named_curve() == "P-384"sv) curve = ::Crypto::Curves::SECP384r1 {}; - coord_size = 48; - } else if (algorithm.named_curve() == "P-521"sv) { + else if (algorithm.named_curve() == "P-521"sv) curve = ::Crypto::Curves::SECP521r1 {}; - coord_size = 66; - } else { + else VERIFY_NOT_REACHED(); - } auto maybe_public_key = curve.visit( [](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Point> { return Error::from_string_literal("noop error"); }, [&](auto instance) { return instance.generate_public_key_point(private_key.d()); }); auto public_key = TRY(maybe_public_key); - auto public_key_bytes = TRY(public_key.to_uncompressed()); - VERIFY(public_key_bytes[0] == 0x04); + auto x_bytes = TRY(public_key.x_bytes()); + auto y_bytes = TRY(public_key.y_bytes()); // 2. Set the x attribute of jwk according to the definition in Section 6.2.1.2 of JSON Web Algorithms [JWA]. - jwk.x = TRY(encode_base64url(public_key_bytes.span().slice(1, coord_size), AK::OmitPadding::Yes)); + jwk.x = TRY(encode_base64url(x_bytes, AK::OmitPadding::Yes)); // 3. Set the y attribute of jwk according to the definition in Section 6.2.1.3 of JSON Web Algorithms [JWA]. - jwk.y = TRY(encode_base64url(public_key_bytes.span().slice(1 + coord_size, coord_size), AK::OmitPadding::Yes)); + jwk.y = TRY(encode_base64url(y_bytes, AK::OmitPadding::Yes)); return {}; }, @@ -3185,9 +3180,8 @@ WebIDL::ExceptionOr> ECDSA::export_key(Bindings::KeyFormat f auto maybe_error = handle.visit( [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { // Set the d attribute of jwk according to the definition in Section 6.2.2.1 of JSON Web Algorithms [JWA]. - auto d_bytes = TRY(ByteBuffer::create_uninitialized(private_key.d().byte_length())); - auto d_size = private_key.d().export_data(d_bytes); - jwk.d = TRY(encode_base64url(d_bytes.span().slice(0, d_size), AK::OmitPadding::Yes)); + auto d_bytes = TRY(private_key.d_bytes()); + jwk.d = TRY(encode_base64url(d_bytes, AK::OmitPadding::Yes)); return {}; }, @@ -3320,7 +3314,7 @@ WebIDL::ExceptionOr, GC::Ref>> ECDH::g return WebIDL::OperationError::create(m_realm, "Failed to create valid crypto instance"_string); auto public_key_data = maybe_public_key_data.release_value(); - auto ec_public_key = ::Crypto::PK::ECPublicKey<> { public_key_data.x, public_key_data.y }; + auto ec_public_key = ::Crypto::PK::ECPublicKey<> { public_key_data }; // 4. Let algorithm be a new EcKeyAlgorithm object. auto algorithm = EcKeyAlgorithm::create(m_realm); @@ -3347,7 +3341,7 @@ WebIDL::ExceptionOr, GC::Ref>> ECDH::g public_key->set_usages({}); // 12. Let privateKey be a new CryptoKey representing the private key of the generated key pair. - auto ec_private_key = ::Crypto::PK::ECPrivateKey<> { private_key_data, {}, ec_public_key }; + auto ec_private_key = ::Crypto::PK::ECPrivateKey<> { private_key_data, public_key_data.size, {}, ec_public_key }; auto private_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { ec_private_key }); // 13. Set the [[type]] internal slot of privateKey to "private" @@ -3424,7 +3418,7 @@ WebIDL::ExceptionOr> ECDH::derive_bits(AlgorithmParams auto maybe_secret = curve.visit( [](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Point> { return Error::from_string_literal("noop error"); }, - [&private_key_data, &public_key_data](auto instance) { return instance.compute_coordinate_point(private_key_data.d(), ::Crypto::Curves::SECPxxxr1Point { public_key_data.x(), public_key_data.y() }); }); + [&private_key_data, &public_key_data](auto instance) { return instance.compute_coordinate_point(private_key_data.d(), public_key_data.to_secpxxxr1_point()); }); if (maybe_secret.is_error()) { auto message = TRY_OR_THROW_OOM(realm.vm(), String::formatted("Failed to compute secret: {}", maybe_secret.error())); @@ -3741,6 +3735,7 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& auto public_key = ::Crypto::PK::ECPublicKey<> { ::Crypto::UnsignedBigInteger::import_data(x_bytes), ::Crypto::UnsignedBigInteger::import_data(y_bytes), + coord_size, }; // If the d field is present: @@ -3758,6 +3753,7 @@ WebIDL::ExceptionOr> ECDH::import_key(AlgorithmParams const& // by interpreting jwk according to Section 6.2.2 of JSON Web Algorithms [JWA]. auto private_key = ::Crypto::PK::ECPrivateKey<> { ::Crypto::UnsignedBigInteger::import_data(d_bytes), + coord_size, {}, public_key, }; @@ -4023,46 +4019,39 @@ WebIDL::ExceptionOr> ECDH::export_key(Bindings::KeyFormat fo auto maybe_error = handle.visit( [&](::Crypto::PK::ECPublicKey<> const& public_key) -> ErrorOr { // 2. Set the x attribute of jwk according to the definition in Section 6.2.1.2 of JSON Web Algorithms [JWA]. - auto x_bytes = TRY(ByteBuffer::create_uninitialized(public_key.x().byte_length())); - auto x_size = public_key.x().export_data(x_bytes); - jwk.x = TRY(encode_base64url(x_bytes.span().slice(0, x_size), AK::OmitPadding::Yes)); + auto x_bytes = TRY(public_key.x_bytes()); + jwk.x = TRY(encode_base64url(x_bytes, AK::OmitPadding::Yes)); // 3. Set the y attribute of jwk according to the definition in Section 6.2.1.3 of JSON Web Algorithms [JWA]. - auto y_bytes = TRY(ByteBuffer::create_uninitialized(public_key.y().byte_length())); - auto y_size = public_key.y().export_data(y_bytes); - jwk.y = TRY(encode_base64url(y_bytes.span().slice(0, y_size), AK::OmitPadding::Yes)); + auto y_bytes = TRY(public_key.y_bytes()); + jwk.y = TRY(encode_base64url(y_bytes, AK::OmitPadding::Yes)); return {}; }, [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { - size_t coord_size; Variant curve; - if (algorithm.named_curve() == "P-256"sv) { + if (algorithm.named_curve() == "P-256"sv) curve = ::Crypto::Curves::SECP256r1 {}; - coord_size = 32; - } else if (algorithm.named_curve() == "P-384"sv) { + else if (algorithm.named_curve() == "P-384"sv) curve = ::Crypto::Curves::SECP384r1 {}; - coord_size = 48; - } else if (algorithm.named_curve() == "P-521"sv) { + else if (algorithm.named_curve() == "P-521"sv) curve = ::Crypto::Curves::SECP521r1 {}; - coord_size = 66; - } else { + else VERIFY_NOT_REACHED(); - } auto maybe_public_key = curve.visit( [](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Point> { return Error::from_string_literal("noop error"); }, [&](auto instance) { return instance.generate_public_key_point(private_key.d()); }); auto public_key = TRY(maybe_public_key); - auto public_key_bytes = TRY(public_key.to_uncompressed()); - VERIFY(public_key_bytes[0] == 0x04); + auto x_bytes = TRY(public_key.x_bytes()); + auto y_bytes = TRY(public_key.y_bytes()); // 2. Set the x attribute of jwk according to the definition in Section 6.2.1.2 of JSON Web Algorithms [JWA]. - jwk.x = TRY(encode_base64url(public_key_bytes.span().slice(1, coord_size), AK::OmitPadding::Yes)); + jwk.x = TRY(encode_base64url(x_bytes, AK::OmitPadding::Yes)); // 3. Set the y attribute of jwk according to the definition in Section 6.2.1.3 of JSON Web Algorithms [JWA]. - jwk.y = TRY(encode_base64url(public_key_bytes.span().slice(1 + coord_size, coord_size), AK::OmitPadding::Yes)); + jwk.y = TRY(encode_base64url(y_bytes, AK::OmitPadding::Yes)); return {}; }, @@ -4078,9 +4067,8 @@ WebIDL::ExceptionOr> ECDH::export_key(Bindings::KeyFormat fo auto maybe_error = handle.visit( [&](::Crypto::PK::ECPrivateKey<> const& private_key) -> ErrorOr { // Set the d attribute of jwk according to the definition in Section 6.2.2.1 of JSON Web Algorithms [JWA]. - auto d_bytes = TRY(ByteBuffer::create_uninitialized(private_key.d().byte_length())); - auto d_size = private_key.d().export_data(d_bytes); - jwk.d = TRY(encode_base64url(d_bytes.span().slice(0, d_size), AK::OmitPadding::Yes)); + auto d_bytes = TRY(private_key.d_bytes()); + jwk.d = TRY(encode_base64url(d_bytes, AK::OmitPadding::Yes)); return {}; }, diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/ec_importKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/ec_importKey.https.any.txt index 48fc5558e41c7..25c0669ddf8b3 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/ec_importKey.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/ec_importKey.https.any.txt @@ -2,8 +2,8 @@ Harness status: OK Found 246 tests -174 Pass -24 Fail +192 Pass +6 Fail 48 Optional Feature Unsupported Pass Good parameters: P-256 bits (spki, buffer(91), {name: ECDSA, namedCurve: P-256}, true, [verify]) Optional Feature Unsupported Good parameters: P-256 bits (spki, buffer(59, compressed), {name: ECDSA, namedCurve: P-256}, true, [verify]) @@ -89,23 +89,23 @@ Pass Empty Usages: P-384 bits (pkcs8, buffer(185), {name: ECDSA, namedCurve: P-3 Pass Good parameters: P-384 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-384}, false, [sign]) Pass Good parameters: P-384 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-384}, false, [sign, sign]) Pass Empty Usages: P-384 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-384}, false, []) -Fail Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, [verify]) +Pass Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, [verify]) Optional Feature Unsupported Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify]) -Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, [verify]) -Fail Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, [verify]) +Pass Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, [verify]) +Pass Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, [verify]) Optional Feature Unsupported Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify]) -Fail Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, []) Optional Feature Unsupported Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, []) Optional Feature Unsupported Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) +Pass Good parameters: P-521 bits (spki, buffer(158), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) Optional Feature Unsupported Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) -Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) -Fail Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) +Pass Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) +Pass Good parameters: P-521 bits (raw, buffer(133), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) Optional Feature Unsupported Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDSA, namedCurve: P-521}, true, [verify, verify]) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDSA, namedCurve: P-521}, true, [sign]) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDSA, namedCurve: P-521}, true, [sign, sign]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDSA, namedCurve: P-521}, true, [sign]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDSA, namedCurve: P-521}, true, [sign, sign]) Pass Empty Usages: P-521 bits (pkcs8, buffer(241), {name: ECDSA, namedCurve: P-521}, true, []) Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign]) Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDSA, namedCurve: P-521}, true, [sign, sign]) @@ -211,16 +211,16 @@ Pass ECDH any JWK alg: P-384 bits (jwk, object(kty, crv, x, y, d, alg), {name: E Pass Good parameters: P-384 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-384}, false, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass ECDH any JWK alg: P-384 bits (jwk, object(kty, crv, x, y, d, alg), {name: ECDH, namedCurve: P-384}, false, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Empty Usages: P-384 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-384}, false, []) -Fail Good parameters: P-521 bits (spki, buffer(158), {name: ECDH, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (spki, buffer(158), {name: ECDH, namedCurve: P-521}, true, []) Optional Feature Unsupported Good parameters: P-521 bits (spki, buffer(90, compressed), {name: ECDH, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDH, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (jwk, object(kty, crv, x, y), {name: ECDH, namedCurve: P-521}, true, []) Pass ECDH any JWK alg: P-521 bits (jwk, object(kty, crv, x, y, alg), {name: ECDH, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (raw, buffer(133), {name: ECDH, namedCurve: P-521}, true, []) +Pass Good parameters: P-521 bits (raw, buffer(133), {name: ECDH, namedCurve: P-521}, true, []) Optional Feature Unsupported Good parameters: P-521 bits (raw, buffer(67, compressed), {name: ECDH, namedCurve: P-521}, true, []) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveKey]) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey]) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveBits]) -Fail Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveKey]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveBits, deriveKey]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveBits]) +Pass Good parameters: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Empty Usages: P-521 bits (pkcs8, buffer(241), {name: ECDH, namedCurve: P-521}, true, []) Fail Good parameters: P-521 bits (jwk, object(kty, crv, x, y, d), {name: ECDH, namedCurve: P-521}, true, [deriveKey]) Pass ECDH any JWK alg: P-521 bits (jwk, object(kty, crv, x, y, d, alg), {name: ECDH, namedCurve: P-521}, true, [deriveKey]) From 3c60510896b5c06944de8d0ef7b6f478b88735de Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Fri, 13 Dec 2024 16:50:26 -0500 Subject: [PATCH 129/237] LibWeb/CSS: Correctly serialize the colorspace name of xyz and xyz-d65 This gives us a +40 subtests passes in: - css/css-color/parsing/color-valid-color-function.html --- Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp | 2 +- .../parsing/color-valid-color-function.txt | 84 +++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp b/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp index 873ba1583ffe5..8d55fc045041c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColor.cpp @@ -51,7 +51,7 @@ StringView string_view_from_color_type(CSSColorValue::ColorType color_type) if (color_type == CSSColorValue::ColorType::XYZD50) return "xyz-d50"sv; if (color_type == CSSColorValue::ColorType::XYZD65) - return "xyz"sv; + return "xyz-d65"sv; VERIFY_NOT_REACHED(); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt index 67026febdfd33..7111cc2345557 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-color/parsing/color-valid-color-function.txt @@ -2,8 +2,8 @@ Harness status: OK Found 306 tests -140 Pass -166 Fail +180 Pass +126 Fail Pass e.style['color'] = "color(srgb 0% 0% 0%)" should set the property value Pass e.style['color'] = "color(srgb 10% 10% 10%)" should set the property value Pass e.style['color'] = "color(srgb .2 .2 25%)" should set the property value @@ -208,26 +208,26 @@ Fail e.style['color'] = "color(display-p3 calc(NaN) 0 0)" should set the propert Fail e.style['color'] = "color(display-p3 calc(0 / 0) 0 0)" should set the property value Fail e.style['color'] = "color(display-p3 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value Fail e.style['color'] = "color(display-p3 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value -Fail e.style['color'] = "color(xyz 0% 0% 0%)" should set the property value -Fail e.style['color'] = "color(xyz 10% 10% 10%)" should set the property value -Fail e.style['color'] = "color(xyz .2 .2 25%)" should set the property value -Fail e.style['color'] = "color(xyz 0 0 0 / 1)" should set the property value -Fail e.style['color'] = "color(xyz 0% 0 0 / 0.5)" should set the property value -Fail e.style['color'] = "color(xyz 20% 0 10/0.5)" should set the property value -Fail e.style['color'] = "color(xyz 20% 0 10/50%)" should set the property value -Fail e.style['color'] = "color(xyz 400% 0 10/50%)" should set the property value -Fail e.style['color'] = "color(xyz 50% -160 160)" should set the property value -Fail e.style['color'] = "color(xyz 50% -200 200)" should set the property value -Fail e.style['color'] = "color(xyz 0 0 0 / -10%)" should set the property value -Fail e.style['color'] = "color(xyz 0 0 0 / 110%)" should set the property value -Fail e.style['color'] = "color(xyz 0 0 0 / 300%)" should set the property value -Fail e.style['color'] = "color(xyz 200 200 200)" should set the property value -Fail e.style['color'] = "color(xyz 200 200 200 / 200)" should set the property value -Fail e.style['color'] = "color(xyz -200 -200 -200)" should set the property value -Fail e.style['color'] = "color(xyz -200 -200 -200 / -200)" should set the property value -Fail e.style['color'] = "color(xyz 200% 200% 200%)" should set the property value -Fail e.style['color'] = "color(xyz 200% 200% 200% / 200%)" should set the property value -Fail e.style['color'] = "color(xyz -200% -200% -200% / -200%)" should set the property value +Pass e.style['color'] = "color(xyz 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(xyz 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(xyz .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(xyz 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(xyz 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(xyz 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(xyz 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz 50% -160 160)" should set the property value +Pass e.style['color'] = "color(xyz 50% -200 200)" should set the property value +Pass e.style['color'] = "color(xyz 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(xyz 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(xyz 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(xyz 200 200 200)" should set the property value +Pass e.style['color'] = "color(xyz 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(xyz -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(xyz -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(xyz 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(xyz 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(xyz -200% -200% -200% / -200%)" should set the property value Fail e.style['color'] = "color(xyz calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value Fail e.style['color'] = "color(xyz calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value Fail e.style['color'] = "color(xyz calc(50%) 50% 0.5)" should set the property value @@ -276,26 +276,26 @@ Fail e.style['color'] = "color(xyz-d50 calc(NaN) 0 0)" should set the property v Fail e.style['color'] = "color(xyz-d50 calc(0 / 0) 0 0)" should set the property value Fail e.style['color'] = "color(xyz-d50 calc(50% + (sign(1em - 10px) * 10%)) 0 0 / 0.5)" should set the property value Fail e.style['color'] = "color(xyz-d50 0.5 0 0 / calc(50% + (sign(1em - 10px) * 10%)))" should set the property value -Fail e.style['color'] = "color(xyz-d65 0% 0% 0%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 10% 10% 10%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 .2 .2 25%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 0 0 0 / 1)" should set the property value -Fail e.style['color'] = "color(xyz-d65 0% 0 0 / 0.5)" should set the property value -Fail e.style['color'] = "color(xyz-d65 20% 0 10/0.5)" should set the property value -Fail e.style['color'] = "color(xyz-d65 20% 0 10/50%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 400% 0 10/50%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 50% -160 160)" should set the property value -Fail e.style['color'] = "color(xyz-d65 50% -200 200)" should set the property value -Fail e.style['color'] = "color(xyz-d65 0 0 0 / -10%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 0 0 0 / 110%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 0 0 0 / 300%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 200 200 200)" should set the property value -Fail e.style['color'] = "color(xyz-d65 200 200 200 / 200)" should set the property value -Fail e.style['color'] = "color(xyz-d65 -200 -200 -200)" should set the property value -Fail e.style['color'] = "color(xyz-d65 -200 -200 -200 / -200)" should set the property value -Fail e.style['color'] = "color(xyz-d65 200% 200% 200%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 200% 200% 200% / 200%)" should set the property value -Fail e.style['color'] = "color(xyz-d65 -200% -200% -200% / -200%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0% 0% 0%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 10% 10% 10%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 .2 .2 25%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0 0 0 / 1)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0% 0 0 / 0.5)" should set the property value +Pass e.style['color'] = "color(xyz-d65 20% 0 10/0.5)" should set the property value +Pass e.style['color'] = "color(xyz-d65 20% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 400% 0 10/50%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 50% -160 160)" should set the property value +Pass e.style['color'] = "color(xyz-d65 50% -200 200)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0 0 0 / -10%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0 0 0 / 110%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 0 0 0 / 300%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 200 200 200)" should set the property value +Pass e.style['color'] = "color(xyz-d65 200 200 200 / 200)" should set the property value +Pass e.style['color'] = "color(xyz-d65 -200 -200 -200)" should set the property value +Pass e.style['color'] = "color(xyz-d65 -200 -200 -200 / -200)" should set the property value +Pass e.style['color'] = "color(xyz-d65 200% 200% 200%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 200% 200% 200% / 200%)" should set the property value +Pass e.style['color'] = "color(xyz-d65 -200% -200% -200% / -200%)" should set the property value Fail e.style['color'] = "color(xyz-d65 calc(0.5 + 1) calc(0.5 - 1) calc(0.5) / calc(-0.5 + 1))" should set the property value Fail e.style['color'] = "color(xyz-d65 calc(50% * 3) calc(-150% / 3) calc(50%) / calc(-50% * 3))" should set the property value Fail e.style['color'] = "color(xyz-d65 calc(50%) 50% 0.5)" should set the property value From bd1cc239df9b1af90cee542e6ab6bbebc69ec358 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:06:04 +0000 Subject: [PATCH 130/237] LibWeb/WebGL2: Implement uniformMatrix{2,3,4}fv --- .../WebGL/WebGL2RenderingContextOverloads.idl | 6 +-- .../LibWeb/GenerateWebGLRenderingContext.cpp | 42 +++++++++++++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index 3ed7b1b6b0faf..4c48f0766b141 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -53,9 +53,9 @@ interface mixin WebGL2RenderingContextOverloads { undefined uniform3iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); undefined uniform4iv(WebGLUniformLocation? location, Int32List v, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); - [FIXME] undefined uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); + undefined uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, optional unsigned long long srcOffset = 0, optional GLuint srcLength = 0); // Reading back pixels // WebGL1: diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 0b30b804454ad..da21c5829550d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -687,19 +687,45 @@ class @class_name@ { if (function.name.starts_with("uniformMatrix"sv)) { auto number_of_matrix_elements = function.name.substring_view(13, 1); function_impl_generator.set("number_of_matrix_elements", number_of_matrix_elements); + + if (webgl_version == 1) { + function_impl_generator.set("array_argument_name", "value"); + } else { + function_impl_generator.set("array_argument_name", "data"); + } + function_impl_generator.append(R"~~~( auto matrix_size = @number_of_matrix_elements@ * @number_of_matrix_elements@; - if (value.has>()) { - auto& data = value.get>(); - glUniformMatrix@number_of_matrix_elements@fv(location->handle(), data.size() / matrix_size, transpose, data.data()); + float const* raw_data = nullptr; + u64 count = 0; + if (@array_argument_name@.has>()) { + auto& vector_data = @array_argument_name@.get>(); + raw_data = vector_data.data(); + count = vector_data.size() / matrix_size; + } else { + auto& typed_array_base = static_cast(*@array_argument_name@.get>()->raw_object()); + auto& float32_array = verify_cast(typed_array_base); + raw_data = float32_array.data().data(); + count = float32_array.array_length().length() / matrix_size; + } +)~~~"); + + if (webgl_version == 2) { + function_impl_generator.append(R"~~~( + raw_data += src_offset; + if (src_length == 0) { + count -= src_offset; + } + + if (src_offset + src_length <= count) { + set_error(GL_INVALID_VALUE); return; } +)~~~"); + } - auto& typed_array_base = static_cast(*value.get>()->raw_object()); - auto& float32_array = verify_cast(typed_array_base); - float const* data = float32_array.data().data(); - auto count = float32_array.array_length().length() / matrix_size; - glUniformMatrix@number_of_matrix_elements@fv(location->handle(), count, transpose, data); + function_impl_generator.append(R"~~~( + glUniformMatrix@number_of_matrix_elements@fv(location->handle(), count, transpose, raw_data); )~~~"); continue; } From 71521a70044dbd1fec854d8d02f0b65efdd7a2af Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:06:40 +0000 Subject: [PATCH 131/237] LibWeb/WebGL2: Implement WebGL 1 version of bufferSubData --- Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index 4c48f0766b141..d01ea2b39a171 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -5,9 +5,10 @@ // https://registry.khronos.org/webgl/specs/latest/2.0/#3.7 interface mixin WebGL2RenderingContextOverloads { // WebGL1: + // FIXME: BufferSource is really a AllowSharedBufferSource undefined bufferData(GLenum target, GLsizeiptr size, GLenum usage); undefined bufferData(GLenum target, BufferSource? srcData, GLenum usage); - [FIXME] undefined bufferSubData(GLenum target, GLintptr dstByteOffset, AllowSharedBufferSource srcData); + undefined bufferSubData(GLenum target, GLintptr dstByteOffset, BufferSource srcData); // WebGL2: [FIXME] undefined bufferData(GLenum target, [AllowShared] ArrayBufferView srcData, GLenum usage, unsigned long long srcOffset, optional GLuint length = 0); [FIXME] undefined bufferSubData(GLenum target, GLintptr dstByteOffset, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset, optional GLuint length = 0); From 8a9d1de1cfd94e906438854ea1b3bd1dff680825 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:06:57 +0000 Subject: [PATCH 132/237] LibWeb/WebGL2: Implement createSampler --- Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 3 ++- .../LibWeb/BindingsGenerator/IDLGenerators.cpp | 1 + .../LibWeb/GenerateWebGLRenderingContext.cpp | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 966a8a858dc1f..cf07f60cf8755 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -842,6 +842,7 @@ class WebGLObject; class WebGLProgram; class WebGLRenderbuffer; class WebGLRenderingContext; +class WebGLSampler; class WebGLShader; class WebGLShaderPrecisionFormat; class WebGLTexture; diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 6c46beb1c61b8..0e4b510bb356b 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -7,6 +7,7 @@ #import #import #import +#import #import #import #import @@ -386,7 +387,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] any getQueryParameter(WebGLQuery query, GLenum pname); // Sampler Objects - [FIXME] WebGLSampler createSampler(); + WebGLSampler createSampler(); [FIXME] undefined deleteSampler(WebGLSampler? sampler); [FIXME] GLboolean isSampler(WebGLSampler? sampler); // [WebGLHandlesContextLoss] [FIXME] undefined bindSampler(GLuint unit, WebGLSampler? sampler); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 8b546ca881699..c2296ce36c25e 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -113,6 +113,7 @@ static bool is_platform_object(Type const& type) "WebGLProgram"sv, "WebGLRenderbuffer"sv, "WebGLRenderingContext"sv, + "WebGLSampler"sv, "WebGLShader"sv, "WebGLShaderPrecisionFormat"sv, "WebGLTexture"sv, diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index da21c5829550d..0c899991cc9f9 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -354,6 +354,7 @@ ErrorOr serenity_main(Main::Arguments arguments) #include #include #include +#include #include #include #include @@ -508,6 +509,15 @@ class @class_name@ { continue; } + if (function.name == "createSampler"sv) { + function_impl_generator.append(R"~~~( + GLuint handle = 0; + glGenSamplers(1, &handle); + return WebGLSampler::create(m_realm, handle); +)~~~"); + continue; + } + if (function.name == "shaderSource"sv) { function_impl_generator.append(R"~~~( Vector strings; From f4f3e446a2c769ebe947444021b41d42c224a9ac Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:15:32 +0000 Subject: [PATCH 133/237] LibWeb/WebGL2: Implement bindSampler --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 0e4b510bb356b..a9d58c5eb3ef9 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -390,7 +390,7 @@ interface mixin WebGL2RenderingContextBase { WebGLSampler createSampler(); [FIXME] undefined deleteSampler(WebGLSampler? sampler); [FIXME] GLboolean isSampler(WebGLSampler? sampler); // [WebGLHandlesContextLoss] - [FIXME] undefined bindSampler(GLuint unit, WebGLSampler? sampler); + undefined bindSampler(GLuint unit, WebGLSampler? sampler); [FIXME] undefined samplerParameteri(WebGLSampler sampler, GLenum pname, GLint param); [FIXME] undefined samplerParameterf(WebGLSampler sampler, GLenum pname, GLfloat param); [FIXME] any getSamplerParameter(WebGLSampler sampler, GLenum pname); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 0c899991cc9f9..c3e7d66a16fc3 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -21,6 +21,7 @@ static bool is_webgl_object_type(StringView type_name) || type_name == "WebGLFramebuffer"sv || type_name == "WebGLProgram"sv || type_name == "WebGLRenderbuffer"sv + || type_name == "WebGLSampler"sv || type_name == "WebGLShader"sv || type_name == "WebGLTexture"sv || type_name == "WebGLUniformLocation"sv From bc9ae79a4746346cfc77f0d130fb233c3cc1e48e Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:42:20 +0000 Subject: [PATCH 134/237] LibWeb/WebGL2: Implement texSubImage2D with ArrayBufferView --- Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index d01ea2b39a171..a4c68fbe25eb2 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -19,7 +19,7 @@ interface mixin WebGL2RenderingContextOverloads { // May throw DOMException undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLenum format, GLenum type, TexImageSource source); - [FIXME] undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); + undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView? pixels); // May throw DOMException [FIXME] undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLenum format, GLenum type, TexImageSource source); From 135ceb387e587c1acb5da52c704c740976e04947 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 14:42:03 +0000 Subject: [PATCH 135/237] LibWeb/WebGL2: Implement fenceSync --- Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/WebGL/Types.h | 4 ++++ Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- Libraries/LibWeb/WebGL/WebGLSync.cpp | 7 ++++--- Libraries/LibWeb/WebGL/WebGLSync.h | 8 ++++++-- .../LibWeb/BindingsGenerator/IDLGenerators.cpp | 1 + .../LibWeb/GenerateWebGLRenderingContext.cpp | 9 +++++++++ 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index cf07f60cf8755..98f27d2e5a81d 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -845,6 +845,7 @@ class WebGLRenderingContext; class WebGLSampler; class WebGLShader; class WebGLShaderPrecisionFormat; +class WebGLSync; class WebGLTexture; class WebGLUniformLocation; class WebGLVertexArrayObject; diff --git a/Libraries/LibWeb/WebGL/Types.h b/Libraries/LibWeb/WebGL/Types.h index bf8d35a25df8b..9d5c6d7a2a560 100644 --- a/Libraries/LibWeb/WebGL/Types.h +++ b/Libraries/LibWeb/WebGL/Types.h @@ -15,4 +15,8 @@ using GLenum = unsigned int; using GLuint = unsigned int; using GLint = int; +// FIXME: This should really be "struct __GLsync*", but the linker doesn't recognise it. +// Since this conflicts with the original definition of GLsync, the suffix "Internal" has been added. +using GLsyncInternal = void*; + } diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index a9d58c5eb3ef9..037f5d92fceab 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -396,7 +396,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] any getSamplerParameter(WebGLSampler sampler, GLenum pname); // Sync objects - [FIXME] WebGLSync? fenceSync(GLenum condition, GLbitfield flags); + WebGLSync? fenceSync(GLenum condition, GLbitfield flags); [FIXME] GLboolean isSync(WebGLSync? sync); // [WebGLHandlesContextLoss] [FIXME] undefined deleteSync(WebGLSync? sync); [FIXME] GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); diff --git a/Libraries/LibWeb/WebGL/WebGLSync.cpp b/Libraries/LibWeb/WebGL/WebGLSync.cpp index cc89e5cdff6ec..46a6f71512b87 100644 --- a/Libraries/LibWeb/WebGL/WebGLSync.cpp +++ b/Libraries/LibWeb/WebGL/WebGLSync.cpp @@ -13,13 +13,14 @@ namespace Web::WebGL { GC_DEFINE_ALLOCATOR(WebGLSync); -GC::Ref WebGLSync::create(JS::Realm& realm, GLuint handle) +GC::Ref WebGLSync::create(JS::Realm& realm, GLsyncInternal handle) { return realm.create(realm, handle); } -WebGLSync::WebGLSync(JS::Realm& realm, GLuint handle) - : WebGLObject(realm, handle) +WebGLSync::WebGLSync(JS::Realm& realm, GLsyncInternal handle) + : WebGLObject(realm, 0) + , m_sync_handle(handle) { } diff --git a/Libraries/LibWeb/WebGL/WebGLSync.h b/Libraries/LibWeb/WebGL/WebGLSync.h index f64195c7125a2..c9616a665bfc0 100644 --- a/Libraries/LibWeb/WebGL/WebGLSync.h +++ b/Libraries/LibWeb/WebGL/WebGLSync.h @@ -16,14 +16,18 @@ class WebGLSync : public WebGLObject { GC_DECLARE_ALLOCATOR(WebGLSync); public: - static GC::Ref create(JS::Realm& realm, GLuint handle); + static GC::Ref create(JS::Realm& realm, GLsyncInternal handle); virtual ~WebGLSync() override; + GLsyncInternal sync_handle() const { return m_sync_handle; } + protected: - explicit WebGLSync(JS::Realm&, GLuint handle); + explicit WebGLSync(JS::Realm&, GLsyncInternal handle); virtual void initialize(JS::Realm&) override; + + GLsyncInternal m_sync_handle { nullptr }; }; } diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index c2296ce36c25e..a017ed109e75d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -116,6 +116,7 @@ static bool is_platform_object(Type const& type) "WebGLSampler"sv, "WebGLShader"sv, "WebGLShaderPrecisionFormat"sv, + "WebGLSync"sv, "WebGLTexture"sv, "WebGLUniformLocation"sv, "WebGLVertexArrayObject"sv, diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index c3e7d66a16fc3..c60babea153d3 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -357,6 +357,7 @@ ErrorOr serenity_main(Main::Arguments arguments) #include #include #include +#include #include #include #include @@ -519,6 +520,14 @@ class @class_name@ { continue; } + if (function.name == "fenceSync"sv) { + function_impl_generator.append(R"~~~( + GLsync handle = glFenceSync(condition, flags); + return WebGLSync::create(m_realm, handle); +)~~~"); + continue; + } + if (function.name == "shaderSource"sv) { function_impl_generator.append(R"~~~( Vector strings; From ff8a9549f1b76460eac115d334fc3f34f1a88a8e Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 17:58:50 +0000 Subject: [PATCH 136/237] LibWeb/WebGL2: Implement clientWaitSync --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 037f5d92fceab..379ec7212a5e6 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -399,7 +399,7 @@ interface mixin WebGL2RenderingContextBase { WebGLSync? fenceSync(GLenum condition, GLbitfield flags); [FIXME] GLboolean isSync(WebGLSync? sync); // [WebGLHandlesContextLoss] [FIXME] undefined deleteSync(WebGLSync? sync); - [FIXME] GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); + GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); [FIXME] undefined waitSync(WebGLSync sync, GLbitfield flags, GLint64 timeout); [FIXME] any getSyncParameter(WebGLSync sync, GLenum pname); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index c60babea153d3..8188c93f4e0eb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -948,6 +948,11 @@ class @class_name@ { gl_call_arguments.append(ByteString::formatted("{} ? {}->handle() : 0", parameter_name, parameter_name)); continue; } + if (parameter.type->name() == "WebGLSync"sv) { + // FIXME: Remove the GLsync cast once sync_handle actually returns the proper GLsync type. + gl_call_arguments.append(ByteString::formatted("(GLsync)({} ? {}->sync_handle() : nullptr)", parameter_name, parameter_name)); + continue; + } if (parameter.type->name() == "BufferSource"sv) { function_impl_generator.set("buffer_source_name", parameter_name); function_impl_generator.append(R"~~~( From 02074871993e77f2dc35363982fb2d75b10dfb39 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 17:59:01 +0000 Subject: [PATCH 137/237] LibWeb/WebGL2: Implement deleteSync --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 379ec7212a5e6..662229b131a29 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -398,7 +398,7 @@ interface mixin WebGL2RenderingContextBase { // Sync objects WebGLSync? fenceSync(GLenum condition, GLbitfield flags); [FIXME] GLboolean isSync(WebGLSync? sync); // [WebGLHandlesContextLoss] - [FIXME] undefined deleteSync(WebGLSync? sync); + undefined deleteSync(WebGLSync? sync); GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); [FIXME] undefined waitSync(WebGLSync sync, GLbitfield flags, GLint64 timeout); [FIXME] any getSyncParameter(WebGLSync sync, GLenum pname); From ee500df7ec5fb12c0b6deefa56aed626c1c7cd1b Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 17:59:20 +0000 Subject: [PATCH 138/237] LibWeb/WebGL2: Implement MAX_SAMPLES parameter --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 8188c93f4e0eb..1e032c21eb399 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -74,9 +74,10 @@ struct NameAndType { StringView type; int element_count { 0 }; } return_type; + Optional webgl_version { OptionalNone {} }; }; -static void generate_get_parameter(SourceGenerator& generator) +static void generate_get_parameter(SourceGenerator& generator, int webgl_version) { Vector const name_to_type = { { "ACTIVE_TEXTURE"sv, { "GLenum"sv } }, @@ -168,6 +169,7 @@ static void generate_get_parameter(SourceGenerator& generator) { "VENDOR"sv, { "DOMString"sv } }, { "VERSION"sv, { "DOMString"sv } }, { "VIEWPORT"sv, { "Int32Array"sv, 4 } }, + { "MAX_SAMPLES"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { @@ -176,6 +178,9 @@ static void generate_get_parameter(SourceGenerator& generator) generator.append(" switch (pname) {"); for (auto const& name_and_type : name_to_type) { + if (name_and_type.webgl_version.has_value() && name_and_type.webgl_version.value() != webgl_version) + continue; + auto const& parameter_name = name_and_type.name; auto const& type_name = name_and_type.return_type.type; @@ -821,7 +826,7 @@ class @class_name@ { } if (function.name == "getParameter"sv) { - generate_get_parameter(function_impl_generator); + generate_get_parameter(function_impl_generator, webgl_version); continue; } From 776328e2e7f26440fdbbea1ed3b129bc6ae60797 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:23:57 +0000 Subject: [PATCH 139/237] LibWeb/WebGL2: Implement getInternalformatParameter --- .../WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 662229b131a29..8099b56963c5c 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -303,7 +303,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined readBuffer(GLenum src); // Renderbuffer objects - [FIXME] any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); + any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); [FIXME] undefined renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); // Texture objects diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 1e032c21eb399..bf1e797db3a8a 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -278,6 +278,26 @@ static void generate_get_buffer_parameter(SourceGenerator& generator) })~~~"); } +static void generate_get_internal_format_parameter(SourceGenerator& generator) +{ + generator.append(R"~~~( + switch (pname) { + case GL_SAMPLES: { + GLint num_sample_counts { 0 }; + glGetInternalformativ(target, internalformat, GL_NUM_SAMPLE_COUNTS, 1, &num_sample_counts); + auto samples_buffer = MUST(ByteBuffer::create_zeroed(num_sample_counts * sizeof(GLint))); + glGetInternalformativ(target, internalformat, GL_SAMPLES, num_sample_counts, reinterpret_cast(samples_buffer.data())); + auto array_buffer = JS::ArrayBuffer::create(m_realm, move(samples_buffer)); + return JS::Int32Array::create(m_realm, num_sample_counts, array_buffer); + } + default: + dbgln("Unknown WebGL internal format parameter name: {:x}", pname); + set_error(GL_INVALID_ENUM); + return JS::js_null(); + } +)~~~"); +} + ErrorOr serenity_main(Main::Arguments arguments) { StringView generated_header_path; @@ -835,6 +855,11 @@ class @class_name@ { continue; } + if (function.name == "getInternalformatParameter") { + generate_get_internal_format_parameter(function_impl_generator); + continue; + } + if (function.name == "getActiveUniform"sv) { function_impl_generator.append(R"~~~( GLint size = 0; From dad30672d9acf24f2ae14f3d3a3513441d2da048 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:24:19 +0000 Subject: [PATCH 140/237] LibWeb/WebGL: Return GL_INVALID_ENUM for unknown buffer parameter names --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index bf1e797db3a8a..64be39b8b7473 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -274,7 +274,9 @@ static void generate_get_buffer_parameter(SourceGenerator& generator) generator.appendln(R"~~~( default: - TODO(); + dbgln("Unknown WebGL buffer parameter name: {:x}", pname); + set_error(GL_INVALID_ENUM); + return JS::js_null(); })~~~"); } From f266705bc6d70f9df3cf40415c2c80efb923ba69 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:26:38 +0000 Subject: [PATCH 141/237] LibWeb/WebGL2: Implement MAX_3D_TEXTURE_SIZE parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 64be39b8b7473..e7f954d0e9db3 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -170,6 +170,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "VERSION"sv, { "DOMString"sv } }, { "VIEWPORT"sv, { "Int32Array"sv, 4 } }, { "MAX_SAMPLES"sv, { "GLint"sv }, 2 }, + { "MAX_3D_TEXTURE_SIZE"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 5cde82ac80c11892d4725ad650eafc35f3543d22 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:29:30 +0000 Subject: [PATCH 142/237] LibWeb/WebGL2: Implement texStorage3D --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 8099b56963c5c..bf3fd7154ddf2 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -308,7 +308,7 @@ interface mixin WebGL2RenderingContextBase { // Texture objects undefined texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); - [FIXME] undefined texStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); + undefined texStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, GLintptr pboOffset); From e6ebec853b19179105db2dc4943c97ba49c278c7 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:47:41 +0000 Subject: [PATCH 143/237] LibWeb/WebGL2: Implement texSubImage3D with ArrayBufferView and offset --- .../LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index bf3fd7154ddf2..21c8d2f10757b 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -323,7 +323,7 @@ interface mixin WebGL2RenderingContextBase { // May throw DOMException [FIXME] undefined texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, TexImageSource source); - [FIXME] undefined texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData, optional unsigned long long srcOffset = 0); + undefined texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData, optional unsigned long long srcOffset = 0); [FIXME] undefined copyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index e7f954d0e9db3..922d23dddcd1f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -670,6 +670,19 @@ class @class_name@ { continue; } + if (function.name == "texSubImage3D"sv && function.overload_index == 0) { + function_impl_generator.append(R"~~~( + void const* pixels_ptr = nullptr; + if (src_data) { + auto const& viewed_array_buffer = src_data->viewed_array_buffer(); + auto const& byte_buffer = viewed_array_buffer->buffer(); + pixels_ptr = byte_buffer.data() + src_offset; + } + glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels_ptr); +)~~~"); + continue; + } + if (function.name == "getShaderParameter"sv) { function_impl_generator.append(R"~~~( GLint result = 0; From 766f4d2ec48f72b33226b2ae47511f29ecc96661 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:57:00 +0000 Subject: [PATCH 144/237] LibWeb/WebGL2: Implement texSubImage2D with ArrayBufferView and offset --- .../WebGL/WebGL2RenderingContextOverloads.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index a4c68fbe25eb2..2d25bd6153f61 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -36,7 +36,7 @@ interface mixin WebGL2RenderingContextOverloads { // May throw DOMException [FIXME] undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, TexImageSource source); - [FIXME] undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); + undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); [FIXME] undefined compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, GLintptr offset); [FIXME] undefined compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, [AllowShared] ArrayBufferView srcData, optional unsigned long long srcOffset = 0, optional GLuint srcLengthOverride = 0); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 922d23dddcd1f..34a75bae2e10f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -670,6 +670,19 @@ class @class_name@ { continue; } + if (webgl_version == 2 && function.name == "texSubImage2D"sv && function.overload_index == 1) { + function_impl_generator.append(R"~~~( + void const* pixels_ptr = nullptr; + if (src_data) { + auto const& viewed_array_buffer = src_data->viewed_array_buffer(); + auto const& byte_buffer = viewed_array_buffer->buffer(); + pixels_ptr = byte_buffer.data() + src_offset; + } + glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_ptr); +)~~~"); + continue; + } + if (function.name == "texSubImage3D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; From 50b4d6554076d98864571a4c80b931c530e04ff5 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 18:57:11 +0000 Subject: [PATCH 145/237] LibWeb/WebGL2: Implement texImage2D with ArrayBufferView and offset --- .../WebGL/WebGL2RenderingContextOverloads.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl index 2d25bd6153f61..37da23a1da449 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextOverloads.idl @@ -30,7 +30,7 @@ interface mixin WebGL2RenderingContextOverloads { // May throw DOMException [FIXME] undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, TexImageSource source); - [FIXME] undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); + undefined texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); [FIXME] undefined texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLintptr pboOffset); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 34a75bae2e10f..4825c4340adbf 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -657,6 +657,19 @@ class @class_name@ { continue; } + if (webgl_version == 2 && function.name == "texImage2D"sv && function.overload_index == 2) { + function_impl_generator.append(R"~~~( + void const* pixels_ptr = nullptr; + if (src_data) { + auto const& viewed_array_buffer = src_data->viewed_array_buffer(); + auto const& byte_buffer = viewed_array_buffer->buffer(); + pixels_ptr = byte_buffer.data() + src_offset; + } + glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_ptr); +)~~~"); + continue; + } + if (function.name == "texSubImage2D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; From 6e42f401f9ff0152d9db39ae4b6828e9a97dec41 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:00:43 +0000 Subject: [PATCH 146/237] LibWeb/WebGL2: Implement texImage3D with ArrayBufferView and offset --- .../WebGL/WebGL2RenderingContextBase.idl | 2 +- .../LibWeb/GenerateWebGLRenderingContext.cpp | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 21c8d2f10757b..ed803428f4391 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -316,7 +316,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, TexImageSource source); undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData); - [FIXME] undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); + undefined texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, unsigned long long srcOffset); [FIXME] undefined texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLintptr pboOffset); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 4825c4340adbf..f800790a2abcb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -595,7 +595,7 @@ class @class_name@ { continue; } - if (function.name == "texImage3D"sv) { + if (function.name == "texImage3D"sv && function.overload_index == 0) { // FIXME: If a WebGLBuffer is bound to the PIXEL_UNPACK_BUFFER target, generates an INVALID_OPERATION error. // FIXME: If srcData is null, a buffer of sufficient size initialized to 0 is passed. // FIXME: If type is specified as FLOAT_32_UNSIGNED_INT_24_8_REV, srcData must be null; otherwise, generates an INVALID_OPERATION error. @@ -614,6 +614,25 @@ class @class_name@ { continue; } + if (function.name == "texImage3D"sv && function.overload_index == 1) { + // FIXME: If a WebGLBuffer is bound to the PIXEL_UNPACK_BUFFER target, generates an INVALID_OPERATION error. + // FIXME: If srcData is null, a buffer of sufficient size initialized to 0 is passed. + // FIXME: If type is specified as FLOAT_32_UNSIGNED_INT_24_8_REV, srcData must be null; otherwise, generates an INVALID_OPERATION error. + // FIXME: If srcData is non-null, the type of srcData must match the type according to the above table; otherwise, generate an INVALID_OPERATION error. + // FIXME: If an attempt is made to call this function with no WebGLTexture bound (see above), generates an INVALID_OPERATION error. + // FIXME: If there's not enough data in srcData starting at srcOffset, generate INVALID_OPERATION. + function_impl_generator.append(R"~~~( + void const* src_data_ptr = nullptr; + if (src_data) { + auto const& viewed_array_buffer = src_data->viewed_array_buffer(); + auto const& byte_buffer = viewed_array_buffer->buffer(); + src_data_ptr = byte_buffer.data() + src_offset; + } + glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, src_data_ptr); +)~~~"); + continue; + } + if (function.name == "texImage2D"sv && function.overload_index == 1) { // FIXME: If this function is called with an ImageData whose data attribute has been neutered, // an INVALID_VALUE error is generated. From 99a501bd09687662967c9918c28685075bde6dce Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:04:09 +0000 Subject: [PATCH 147/237] LibWeb/WebGL2: Implement MAX_ARRAY_TEXTURE_LAYERS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index f800790a2abcb..f9cbe78265e5f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -171,6 +171,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "VIEWPORT"sv, { "Int32Array"sv, 4 } }, { "MAX_SAMPLES"sv, { "GLint"sv }, 2 }, { "MAX_3D_TEXTURE_SIZE"sv, { "GLint"sv }, 2 }, + { "MAX_ARRAY_TEXTURE_LAYERS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From b6388abb9bfbe6128f810078c916b0f397c7069e Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:08:29 +0000 Subject: [PATCH 148/237] LibWeb/WebGL2: Implement MAX_COLOR_ATTACHMENTS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index f9cbe78265e5f..f3f53895f50dd 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -172,6 +172,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_SAMPLES"sv, { "GLint"sv }, 2 }, { "MAX_3D_TEXTURE_SIZE"sv, { "GLint"sv }, 2 }, { "MAX_ARRAY_TEXTURE_LAYERS"sv, { "GLint"sv }, 2 }, + { "MAX_COLOR_ATTACHMENTS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 791c258754e0bdfbe39d192e1a0795b8233053b5 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:10:30 +0000 Subject: [PATCH 149/237] LibWeb/WebGL2: Implement MAX_VERTEX_UNIFORM_COMPONENTS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index f3f53895f50dd..015a1c360a0de 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -173,6 +173,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_3D_TEXTURE_SIZE"sv, { "GLint"sv }, 2 }, { "MAX_ARRAY_TEXTURE_LAYERS"sv, { "GLint"sv }, 2 }, { "MAX_COLOR_ATTACHMENTS"sv, { "GLint"sv }, 2 }, + { "MAX_VERTEX_UNIFORM_COMPONENTS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 0e5e0d922f4a3ac8d0d714a15284f27731383505 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:15:20 +0000 Subject: [PATCH 150/237] LibWeb/WebGL2: Implement MAX_UNIFORM_BLOCK_SIZE parameter --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 015a1c360a0de..4f1a0f72c5fd4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -174,6 +174,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_ARRAY_TEXTURE_LAYERS"sv, { "GLint"sv }, 2 }, { "MAX_COLOR_ATTACHMENTS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_COMPONENTS"sv, { "GLint"sv }, 2 }, + { "MAX_UNIFORM_BLOCK_SIZE"sv, { "GLint64"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { @@ -199,6 +200,12 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version GLint result; glGetIntegerv(GL_@parameter_name@, &result); return JS::Value(result); +)~~~"); + } else if (type_name == "GLint64"sv) { + impl_generator.append(R"~~~( + GLint64 result; + glGetInteger64v(GL_@parameter_name@, &result); + return JS::Value(static_cast(result)); )~~~"); } else if (type_name == "DOMString"sv) { impl_generator.append(R"~~~( From 2adacbc8bb1e9250bcd2e3da18e56e0ababa3974 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:20:52 +0000 Subject: [PATCH 151/237] LibWeb/WebGL2: Implement MAX_UNIFORM_BUFFER_BINDINGS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 4f1a0f72c5fd4..9456e19f2316f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -175,6 +175,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_COLOR_ATTACHMENTS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_COMPONENTS"sv, { "GLint"sv }, 2 }, { "MAX_UNIFORM_BLOCK_SIZE"sv, { "GLint64"sv }, 2 }, + { "MAX_UNIFORM_BUFFER_BINDINGS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 8b07af98014928ccd8bcf2f09e619ba58c720bf2 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:21:35 +0000 Subject: [PATCH 152/237] LibWeb/WebGL2: Implement bindBufferBase --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index ed803428f4391..262768815e940 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -416,7 +416,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined resumeTransformFeedback(); // Uniform Buffer Objects and Transform Feedback Buffers - [FIXME] undefined bindBufferBase(GLenum target, GLuint index, WebGLBuffer? buffer); + undefined bindBufferBase(GLenum target, GLuint index, WebGLBuffer? buffer); [FIXME] undefined bindBufferRange(GLenum target, GLuint index, WebGLBuffer? buffer, GLintptr offset, GLsizeiptr size); [FIXME] any getIndexedParameter(GLenum target, GLuint index); [FIXME] sequence? getUniformIndices(WebGLProgram program, sequence uniformNames); From 1fc8353da0755acb533d3fee29cbcb1a68efb220 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:25:21 +0000 Subject: [PATCH 153/237] LibWeb/WebGL2: Implement UNIFORM_BUFFER_OFFSET_ALIGNMENT parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 9456e19f2316f..cbf1c7d98df38 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -176,6 +176,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_VERTEX_UNIFORM_COMPONENTS"sv, { "GLint"sv }, 2 }, { "MAX_UNIFORM_BLOCK_SIZE"sv, { "GLint64"sv }, 2 }, { "MAX_UNIFORM_BUFFER_BINDINGS"sv, { "GLint"sv }, 2 }, + { "UNIFORM_BUFFER_OFFSET_ALIGNMENT"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From db89b478ff0ef1f2809798dcba0b6ad6ff6acee9 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:35:10 +0000 Subject: [PATCH 154/237] LibWeb/WebGL2: Implement renderbufferStorageMultisample --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 262768815e940..70b5ff39234b6 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -304,7 +304,7 @@ interface mixin WebGL2RenderingContextBase { // Renderbuffer objects any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); - [FIXME] undefined renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + undefined renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); // Texture objects undefined texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); From a19fb0a7594f77998102df66a35d1c32cfee863d Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:37:11 +0000 Subject: [PATCH 155/237] LibWeb/WebGL2: Implement MAX_DRAW_BUFFERS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index cbf1c7d98df38..f26556c11363c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -177,6 +177,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_UNIFORM_BLOCK_SIZE"sv, { "GLint64"sv }, 2 }, { "MAX_UNIFORM_BUFFER_BINDINGS"sv, { "GLint"sv }, 2 }, { "UNIFORM_BUFFER_OFFSET_ALIGNMENT"sv, { "GLint"sv }, 2 }, + { "MAX_DRAW_BUFFERS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From f1d194a97a90315f776d83f800f2f0a98476ba9b Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:37:26 +0000 Subject: [PATCH 156/237] LibWeb/WebGL2: Implement MAX_VERTEX_UNIFORM_BLOCKS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index f26556c11363c..d0ba37f50c5d4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -178,6 +178,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_UNIFORM_BUFFER_BINDINGS"sv, { "GLint"sv }, 2 }, { "UNIFORM_BUFFER_OFFSET_ALIGNMENT"sv, { "GLint"sv }, 2 }, { "MAX_DRAW_BUFFERS"sv, { "GLint"sv }, 2 }, + { "MAX_VERTEX_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 5d383fdd11ef60a6558e86fff3b4ce206637fd96 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:38:57 +0000 Subject: [PATCH 157/237] LibWeb/WebGL2: Implement MAX_FRAGMENT_INPUT_COMPONENTS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index d0ba37f50c5d4..3683ab1e816c8 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -179,6 +179,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "UNIFORM_BUFFER_OFFSET_ALIGNMENT"sv, { "GLint"sv }, 2 }, { "MAX_DRAW_BUFFERS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, + { "MAX_FRAGMENT_INPUT_COMPONENTS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From ced5eea610b039cf6758f3b81e226d1b11037a61 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 19:46:33 +0000 Subject: [PATCH 158/237] LibWeb/WebGL2: Implement MAX_COMBINED_UNIFORM_BLOCKS parameter --- .../CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 3683ab1e816c8..8b72280b9dc10 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -180,6 +180,7 @@ static void generate_get_parameter(SourceGenerator& generator, int webgl_version { "MAX_DRAW_BUFFERS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, { "MAX_FRAGMENT_INPUT_COMPONENTS"sv, { "GLint"sv }, 2 }, + { "MAX_COMBINED_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { From 4a4263d869d5ac8ba1236d6413ae79803f58fb20 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 21:32:59 +0000 Subject: [PATCH 159/237] LibWeb/WebGL2: Implement readBuffer --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 70b5ff39234b6..710d1bbe1faa4 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -300,7 +300,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined framebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture? texture, GLint level, GLint layer); [FIXME] undefined invalidateFramebuffer(GLenum target, sequence attachments); [FIXME] undefined invalidateSubFramebuffer(GLenum target, sequence attachments, GLint x, GLint y, GLsizei width, GLsizei height); - [FIXME] undefined readBuffer(GLenum src); + undefined readBuffer(GLenum src); // Renderbuffer objects any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); From 160c15444b581520b42aa4bddb2c24cc857d3a6a Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 21:33:15 +0000 Subject: [PATCH 160/237] LibWeb/WebGL2: Implement blitFramebuffer --- Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl index 710d1bbe1faa4..cc1f80c825bf6 100644 --- a/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl +++ b/Libraries/LibWeb/WebGL/WebGL2RenderingContextBase.idl @@ -296,7 +296,7 @@ interface mixin WebGL2RenderingContextBase { [FIXME] undefined getBufferSubData(GLenum target, GLintptr srcByteOffset, [AllowShared] ArrayBufferView dstBuffer, optional unsigned long long dstOffset = 0, optional GLuint length = 0); // Framebuffer objects - [FIXME] undefined blitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + undefined blitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); [FIXME] undefined framebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture? texture, GLint level, GLint layer); [FIXME] undefined invalidateFramebuffer(GLenum target, sequence attachments); [FIXME] undefined invalidateSubFramebuffer(GLenum target, sequence attachments, GLint x, GLint y, GLsizei width, GLsizei height); From 03ac6e6e87488fdc638cd3a84b2c59890bdba3cc Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sat, 14 Dec 2024 01:57:31 +0400 Subject: [PATCH 161/237] LibGC: Preallocate space before dumping GC graph Speeds up the append_gc_graph function by preallocating space. This change reduces the time taken to dump the GC graph by 4% on about:blank. --- Libraries/LibGC/Heap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/LibGC/Heap.cpp b/Libraries/LibGC/Heap.cpp index f6be03c1bd35d..2617b04c77f78 100644 --- a/Libraries/LibGC/Heap.cpp +++ b/Libraries/LibGC/Heap.cpp @@ -119,6 +119,7 @@ class GraphConstructorVisitor final : public Cell::Visitor { m_all_live_heap_blocks.set(&block); return IterationDecision::Continue; }); + m_work_queue.ensure_capacity(roots.size()); for (auto& [root, root_origin] : roots) { auto& graph_node = m_graph.ensure(bit_cast(root)); From 0a901434208ee424b71f2d717f7209f3d29ee4a2 Mon Sep 17 00:00:00 2001 From: stasoid Date: Sat, 14 Dec 2024 14:30:20 +0500 Subject: [PATCH 162/237] AK: Fix BumpAllocator.h compilation errors on Windows --- AK/BumpAllocator.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AK/BumpAllocator.h b/AK/BumpAllocator.h index 48f325d766b30..26f543d99403c 100644 --- a/AK/BumpAllocator.h +++ b/AK/BumpAllocator.h @@ -13,8 +13,8 @@ #if !defined(AK_OS_WINDOWS) # include #else -extern "C" __declspec(dllimport) void* __stdcall VirtualAlloc(size_t dwSize, u32 flAllocationType, u32 flProtect); -extern "C" __declspec(dllimport) bool __stdcall VirtualFree(void* lpAddress, size_t dwSize, u32 dwFreeType); +extern "C" __declspec(dllimport) void* __stdcall VirtualAlloc(void* lpAddress, size_t size, u32 flAllocationType, u32 flProtect); +extern "C" __declspec(dllimport) bool __stdcall VirtualFree(void* lpAddress, size_t size, u32 dwFreeType); # define MEM_COMMIT 0x00001000 # define PAGE_READWRITE 0x04 # define MEM_RELEASE 0x00008000 @@ -82,7 +82,7 @@ class BumpAllocator { if constexpr (use_mmap) { #if defined(AK_OS_WINDOWS) - VirtualFree((LPVOID)chunk, m_chunk_size, MEM_RELEASE); + VirtualFree((void*)chunk, m_chunk_size, MEM_RELEASE); #else munmap((void*)chunk, m_chunk_size); #endif From 18dddaa7424455d0cfb15c0fa05057728e4783c8 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 15 Dec 2024 02:59:23 +1300 Subject: [PATCH 163/237] LibWeb/HTML: Use DOM's post-connection steps for iframe elements See: https://github.com/whatwg/html/commit/c8ec987d1 --- Libraries/LibWeb/HTML/HTMLIFrameElement.cpp | 18 ++-- Libraries/LibWeb/HTML/HTMLIFrameElement.h | 2 +- ...ppendChild-script-and-iframe.tentative.txt | 9 ++ ...pendChild-script-and-iframe.tentative.html | 89 +++++++++++++++++++ 4 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html diff --git a/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp b/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp index 022936135c5aa..23013d3dbf59a 100644 --- a/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp @@ -62,28 +62,22 @@ void HTMLIFrameElement::attribute_changed(FlyString const& name, Optional(shadow_including_root())) - return; - DOM::Document& document = verify_cast(shadow_including_root()); // NOTE: The check for "not fully active" is to prevent a crash on the dom/nodes/node-appendchild-crash.html WPT test. if (!document.browsing_context() || !document.is_fully_active()) return; - // 2. Create a new child navigable for insertedNode. + // The iframe HTML element post-connection steps, given insertedNode, are: + // 1. Create a new child navigable for insertedNode. MUST(create_new_child_navigable(GC::create_function(realm().heap(), [this] { - // FIXME: 3. If insertedNode has a sandbox attribute, then parse the sandboxing directive given the attribute's + // FIXME: 2. If insertedNode has a sandbox attribute, then parse the sandboxing directive given the attribute's // value and insertedNode's iframe sandboxing flag set. - // 4. Process the iframe attributes for insertedNode, with initialInsertion set to true. + // 3. Process the iframe attributes for insertedNode, with initialInsertion set to true. process_the_iframe_attributes(true); set_content_navigable_initialized(); }))); diff --git a/Libraries/LibWeb/HTML/HTMLIFrameElement.h b/Libraries/LibWeb/HTML/HTMLIFrameElement.h index 085df19052cbc..221da71bc6fd7 100644 --- a/Libraries/LibWeb/HTML/HTMLIFrameElement.h +++ b/Libraries/LibWeb/HTML/HTMLIFrameElement.h @@ -41,7 +41,7 @@ class HTMLIFrameElement final virtual void initialize(JS::Realm&) override; // ^DOM::Element - virtual void inserted() override; + virtual void post_connection() override; virtual void removed_from(Node*) override; virtual void attribute_changed(FlyString const& name, Optional const& old_value, Optional const& value, Optional const& namespace_) override; virtual i32 default_tab_index_value() const override; diff --git a/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.txt new file mode 100644 index 0000000000000..a79a2fd04867a --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.txt @@ -0,0 +1,9 @@ +Harness status: OK + +Found 4 tests + +4 Pass +Pass Script inserted after an iframe in the same appendChild() call can observe the iframe's non-null contentWindow +Pass A script inserted atomically before an iframe (using a div) does not observe the iframe's contentWindow, since the 'script running' and 'iframe setup' both happen in order, after DOM insertion completes +Pass A script inserted atomically before an iframe (using a DocumentFragment) does not observe the iframe's contentWindow, since the 'script running' and 'iframe setup' both happen in order, after DOM insertion completes +Pass A script inserted atomically before an iframe (using a append() with multiple arguments) does not observe the iframe's contentWindow, since the 'script running' and 'iframe setup' both happen in order, after DOM insertion completes \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html new file mode 100644 index 0000000000000..d3277fff8ac7c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html @@ -0,0 +1,89 @@ + + +Node.appendChild: inserting script and iframe + + + + From 5947c37637f8ea3d4c323a04e5e5dfe9fad5a4d7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 13 Dec 2024 11:50:55 -0500 Subject: [PATCH 164/237] LibJS: Return the allocated dst register from deleting super properties Even though calling delete on a super property will ultimately throw a ReferenceError, we must return the allocated register for the result of the delete operation (which would normally be a boolean). If the delete operation is used in a return statement, the bytecode generator for the return statement must be able to assume the statement had some output. --- Libraries/LibJS/Bytecode/Generator.cpp | 2 +- Libraries/LibJS/Tests/operators/delete-basic.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Libraries/LibJS/Bytecode/Generator.cpp b/Libraries/LibJS/Bytecode/Generator.cpp index f4c99673a1c43..dcd7b04efa430 100644 --- a/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Libraries/LibJS/Bytecode/Generator.cpp @@ -812,7 +812,7 @@ CodeGenerationErrorOr> Generator::emit_delete_reference( emit(dst, *super_reference.base, *super_reference.this_value, identifier_table_ref); } - return Optional {}; + return dst; } auto object = TRY(expression.object().generate_bytecode(*this)).value(); diff --git a/Libraries/LibJS/Tests/operators/delete-basic.js b/Libraries/LibJS/Tests/operators/delete-basic.js index f41269c5fc9e7..fc6d58b057a2b 100644 --- a/Libraries/LibJS/Tests/operators/delete-basic.js +++ b/Libraries/LibJS/Tests/operators/delete-basic.js @@ -93,6 +93,13 @@ test("deleting super property", () => { } } + class D { + static foo() { + const deleter = () => delete super.foo; + deleter(); + } + } + const obj = new B(); expect(() => { obj.bar(); @@ -106,6 +113,10 @@ test("deleting super property", () => { expect(() => { C.foo(); }).toThrowWithMessage(ReferenceError, "Can't delete a property on 'super'"); + + expect(() => { + D.foo(); + }).toThrowWithMessage(ReferenceError, "Can't delete a property on 'super'"); }); test("deleting an object computed property coerces the object to a property key", () => { From 40cb36607cd6c5e428d12c903d5d5c478d324b8e Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Thu, 12 Dec 2024 19:48:32 +0000 Subject: [PATCH 165/237] LibWeb: Include namespace parameter in `associated_attribute_changed()` --- Libraries/LibWeb/HTML/FormAssociatedElement.h | 4 ++-- Libraries/LibWeb/HTML/HTMLImageElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLImageElement.h | 2 +- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLInputElement.h | 2 +- Libraries/LibWeb/HTML/HTMLObjectElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLObjectElement.h | 2 +- Libraries/LibWeb/HTML/HTMLOutputElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLOutputElement.h | 2 +- Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLTextAreaElement.h | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index bf2c53f74e29a..3e02ad553d4f7 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -49,7 +49,7 @@ private: { \ ElementBaseClass::attribute_changed(name, old_value, value, namespace_); \ form_node_attribute_changed(name, value); \ - form_associated_element_attribute_changed(name, value); \ + form_associated_element_attribute_changed(name, value, namespace_); \ } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#selection-direction @@ -110,7 +110,7 @@ class FormAssociatedElement { virtual void form_associated_element_was_inserted() { } virtual void form_associated_element_was_removed(DOM::Node*) { } - virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&) { } + virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&, Optional const&) { } void form_node_was_inserted(); void form_node_was_removed(); diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.cpp b/Libraries/LibWeb/HTML/HTMLImageElement.cpp index 2d5207c1aff1a..fbc453ac19978 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLImageElement.cpp @@ -113,7 +113,7 @@ void HTMLImageElement::apply_presentational_hints(CSS::StyleProperties& style) c }); } -void HTMLImageElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value) +void HTMLImageElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) { if (name == HTML::AttributeNames::crossorigin) { m_cors_setting = cors_setting_attribute_from_keyword(value); diff --git a/Libraries/LibWeb/HTML/HTMLImageElement.h b/Libraries/LibWeb/HTML/HTMLImageElement.h index a7b06ee28ad05..ddc8854e8bc59 100644 --- a/Libraries/LibWeb/HTML/HTMLImageElement.h +++ b/Libraries/LibWeb/HTML/HTMLImageElement.h @@ -38,7 +38,7 @@ class HTMLImageElement final public: virtual ~HTMLImageElement() override; - virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value) override; + virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) override; Optional alternative_text() const override { diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 25d96221fa77a..607ce240fb559 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1195,7 +1195,7 @@ void HTMLInputElement::did_lose_focus() commit_pending_changes(); } -void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value) +void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) { if (name == HTML::AttributeNames::checked) { if (!value.has_value()) { diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index bf2df331779b0..0990ab3153a66 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -185,7 +185,7 @@ class HTMLInputElement final virtual void form_associated_element_was_inserted() override; virtual void form_associated_element_was_removed(DOM::Node*) override; - virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&) override; + virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&, Optional const&) override; virtual WebIDL::ExceptionOr cloned(Node&, bool) override; diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp index aba1d2b115caa..cbc48c862dee3 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.cpp @@ -78,7 +78,7 @@ void HTMLObjectElement::visit_edges(Cell::Visitor& visitor) visitor.visit(m_document_observer); } -void HTMLObjectElement::form_associated_element_attribute_changed(FlyString const& name, Optional const&) +void HTMLObjectElement::form_associated_element_attribute_changed(FlyString const& name, Optional const&, Optional const&) { // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element // Whenever one of the following conditions occur: diff --git a/Libraries/LibWeb/HTML/HTMLObjectElement.h b/Libraries/LibWeb/HTML/HTMLObjectElement.h index acbe7c87777d7..94a1c70ab99ba 100644 --- a/Libraries/LibWeb/HTML/HTMLObjectElement.h +++ b/Libraries/LibWeb/HTML/HTMLObjectElement.h @@ -33,7 +33,7 @@ class HTMLObjectElement final public: virtual ~HTMLObjectElement() override; - virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value) override; + virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const& namespace_) override; virtual void form_associated_element_was_removed(DOM::Node*) override; String data() const; diff --git a/Libraries/LibWeb/HTML/HTMLOutputElement.cpp b/Libraries/LibWeb/HTML/HTMLOutputElement.cpp index 12fc2e06eebbd..dae7cfa3cb744 100644 --- a/Libraries/LibWeb/HTML/HTMLOutputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLOutputElement.cpp @@ -32,7 +32,7 @@ void HTMLOutputElement::visit_edges(Cell::Visitor& visitor) visitor.visit(m_html_for); } -void HTMLOutputElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value) +void HTMLOutputElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) { if (name == HTML::AttributeNames::for_) { if (m_html_for) diff --git a/Libraries/LibWeb/HTML/HTMLOutputElement.h b/Libraries/LibWeb/HTML/HTMLOutputElement.h index 849e3f985f0e4..6c066dd78e1d3 100644 --- a/Libraries/LibWeb/HTML/HTMLOutputElement.h +++ b/Libraries/LibWeb/HTML/HTMLOutputElement.h @@ -63,7 +63,7 @@ class HTMLOutputElement final virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor& visitor) override; - virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value) override; + virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) override; GC::Ptr m_html_for; diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index 89f2bdac319dc..bd9d264af2780 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -439,7 +439,7 @@ void HTMLTextAreaElement::children_changed() } } -void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value) +void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const&) { if (name == HTML::AttributeNames::placeholder) { if (m_placeholder_text_node) diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index 3053f63d2da05..92a7291338a2f 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -68,7 +68,7 @@ class HTMLTextAreaElement final virtual void form_associated_element_was_inserted() override; virtual void form_associated_element_was_removed(DOM::Node*) override; - virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&) override; + virtual void form_associated_element_attribute_changed(FlyString const&, Optional const&, Optional const&) override; virtual void children_changed() override; From 022141647ae4d8706fdd04ee0ddb909dce592f95 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Thu, 12 Dec 2024 19:53:04 +0000 Subject: [PATCH 166/237] LibWeb: Implement `PopoverInvokerElement` attribute change steps PopoverInvokerElement's explicitly set attr-element should be set to null whenever the value of the `popovertarget` content attribute is changed. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/HTML/HTMLButtonElement.cpp | 5 ++ Libraries/LibWeb/HTML/HTMLButtonElement.h | 2 + .../LibWeb/HTML/PopoverInvokerElement.cpp | 32 +++++++++++ Libraries/LibWeb/HTML/PopoverInvokerElement.h | 6 +-- .../popovers/popovertarget-reflection.txt | 6 +++ .../popovers/popovertarget-reflection.html | 53 +++++++++++++++++++ 7 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 Libraries/LibWeb/HTML/PopoverInvokerElement.cpp create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popovertarget-reflection.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popovertarget-reflection.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 2c0ec8c0a1f8d..598d6457a0fc8 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -441,6 +441,7 @@ set(SOURCES HTML/Numbers.cpp HTML/PageTransitionEvent.cpp HTML/PolicyContainers.cpp + HTML/PopoverInvokerElement.cpp HTML/PopStateEvent.cpp HTML/Parser/Entities.cpp HTML/Parser/HTMLEncodingDetection.cpp diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp index 8fdfe3e843268..90e8e9207b87e 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp @@ -45,6 +45,11 @@ WebIDL::ExceptionOr HTMLButtonElement::set_type(String const& type) return set_attribute(HTML::AttributeNames::type, type); } +void HTMLButtonElement::form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const& namespace_) +{ + PopoverInvokerElement::associated_attribute_changed(name, value, namespace_); +} + void HTMLButtonElement::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Libraries/LibWeb/HTML/HTMLButtonElement.h index b1531848d87ac..44876b2a88ef5 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.h +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.h @@ -40,6 +40,8 @@ class HTMLButtonElement final TypeAttributeState type_state() const; WebIDL::ExceptionOr set_type(String const&); + virtual void form_associated_element_attribute_changed(FlyString const& name, Optional const& value, Optional const& namespace_) override; + // ^EventTarget // https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute:the-button-element // https://html.spec.whatwg.org/multipage/interaction.html#focusable-area diff --git a/Libraries/LibWeb/HTML/PopoverInvokerElement.cpp b/Libraries/LibWeb/HTML/PopoverInvokerElement.cpp new file mode 100644 index 0000000000000..fae92643af46e --- /dev/null +++ b/Libraries/LibWeb/HTML/PopoverInvokerElement.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::HTML { + +void PopoverInvokerElement::associated_attribute_changed(FlyString const& name, Optional const&, Optional const& namespace_) +{ + // From: https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributess + // For element reflected targets only: the following attribute change steps, given element, localName, oldValue, value, and namespace, + // are used to synchronize between the content attribute and the IDL attribute: + + // 1. If localName is not attr or namespace is not null, then return. + if (name != HTML::AttributeNames::popovertarget || namespace_.has_value()) + return; + + // 2. Set element's explicitly set attr-elements to null. + m_popover_target_element = nullptr; +} + +void PopoverInvokerElement::visit_edges(JS::Cell::Visitor& visitor) +{ + visitor.visit(m_popover_target_element); +} + +} diff --git a/Libraries/LibWeb/HTML/PopoverInvokerElement.h b/Libraries/LibWeb/HTML/PopoverInvokerElement.h index 0187eaaeeac85..6cd95d0bcefd1 100644 --- a/Libraries/LibWeb/HTML/PopoverInvokerElement.h +++ b/Libraries/LibWeb/HTML/PopoverInvokerElement.h @@ -22,10 +22,8 @@ class PopoverInvokerElement { void set_popover_target_element(GC::Ptr value) { m_popover_target_element = value; } protected: - void visit_edges(JS::Cell::Visitor& visitor) - { - visitor.visit(m_popover_target_element); - } + void visit_edges(JS::Cell::Visitor&); + void associated_attribute_changed(FlyString const& name, Optional const& value, Optional const& namespace_); private: GC::Ptr m_popover_target_element; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popovertarget-reflection.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popovertarget-reflection.txt new file mode 100644 index 0000000000000..2c748ed1e34dd --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popovertarget-reflection.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Element attribute reflection of popoverTargetElement/popovertarget should be kept in sync. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popovertarget-reflection.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popovertarget-reflection.html new file mode 100644 index 0000000000000..4b1a1d8a3e195 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popovertarget-reflection.html @@ -0,0 +1,53 @@ + + + + + + + + + +
    popover
    + + From 03275419994f7cf0ec677e6a39a46a812161fd4b Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 21:10:28 +0100 Subject: [PATCH 167/237] LibWeb: Implement delete_a_database for IndexedDB --- .../LibWeb/IndexedDB/Internal/Algorithms.cpp | 76 +++++++++++++++++++ .../LibWeb/IndexedDB/Internal/Algorithms.h | 1 + 2 files changed, 77 insertions(+) diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 69cc8b227f06c..6badf10ec29fd 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -346,4 +346,80 @@ void upgrade_a_database(JS::Realm& realm, GC::Ref connection, u64 v // FIXME: 11. Wait for transaction to finish. } +// https://w3c.github.io/IndexedDB/#deleting-a-database +WebIDL::ExceptionOr delete_a_database(JS::Realm& realm, StorageAPI::StorageKey storage_key, String name, GC::Ref request) +{ + // 1. Let queue be the connection queue for storageKey and name. + auto& queue = ConnectionQueueHandler::for_key_and_name(storage_key, name); + + // 2. Add request to queue. + queue.append(request); + + // 3. Wait until all previous requests in queue have been processed. + HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [queue, request]() { + return queue.all_previous_requests_processed(request); + })); + + // 4. Let db be the database named name in storageKey, if one exists. Otherwise, return 0 (zero). + auto maybe_db = Database::for_key_and_name(storage_key, name); + if (!maybe_db.has_value()) + return 0; + + auto db = maybe_db.value(); + + // 5. Let openConnections be the set of all connections associated with db. + auto open_connections = db->associated_connections(); + + // 6. For each entry of openConnections that does not have its close pending flag set to true, + // queue a task to fire a version change event named versionchange at entry with db’s version and null. + IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size(); + IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0; + for (auto const& entry : open_connections) { + if (!entry->close_pending()) { + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, entry, db, &events_fired]() { + fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), {}); + events_fired++; + })); + } else { + events_fired++; + } + } + + // 7. Wait for all of the events to be fired. + HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&events_to_fire, &events_fired]() { + return events_fired == events_to_fire; + })); + + // 8. If any of the connections in openConnections are still not closed, queue a task to fire a version change event named blocked at request with db’s version and null. + for (auto const& entry : open_connections) { + if (entry->state() != IDBDatabase::ConnectionState::Closed) { + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, entry, db]() { + fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), {}); + })); + } + } + + // 9. Wait until all connections in openConnections are closed. + HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() { + for (auto const& entry : open_connections) { + if (entry->state() != IDBDatabase::ConnectionState::Closed) { + return false; + } + } + + return true; + })); + + // 10. Let version be db’s version. + auto version = db->version(); + + // 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException). + auto maybe_deleted = Database::delete_for_key_and_name(storage_key, name); + if (maybe_deleted.is_error()) + return WebIDL::OperationError::create(realm, "Unable to delete database"_string); + + // 12. Return version. + return version; +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index dddcdbc560b70..806d1fe12c32d 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -18,5 +18,6 @@ bool fire_a_version_change_event(JS::Realm&, FlyString const&, GC::Ref convert_a_value_to_a_key(JS::Realm&, JS::Value, Vector = {}); void close_a_database_connection(IDBDatabase&, bool forced = false); void upgrade_a_database(JS::Realm&, GC::Ref, u64, GC::Ref); +WebIDL::ExceptionOr delete_a_database(JS::Realm&, StorageAPI::StorageKey, String, GC::Ref); } From 452506964c5a73b7035819c11581706446798e52 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 21:11:57 +0100 Subject: [PATCH 168/237] LibWeb: Add const qualifiers to Vector iteration in IndexedDB --- Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 6badf10ec29fd..6c0a66a9043f0 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -81,7 +81,7 @@ WebIDL::ExceptionOr> open_a_database_connection(JS::Realm& // queue a task to fire a version change event named versionchange at entry with db’s version and version. IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size(); IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0; - for (auto& entry : open_connections) { + for (auto const& entry : open_connections) { if (!entry->close_pending()) { HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, entry, db, version, &events_fired]() { fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), version); @@ -99,7 +99,7 @@ WebIDL::ExceptionOr> open_a_database_connection(JS::Realm& // 4. If any of the connections in openConnections are still not closed, // queue a task to fire a version change event named blocked at request with db’s version and version. - for (auto& entry : open_connections) { + for (auto const& entry : open_connections) { if (entry->state() != IDBDatabase::ConnectionState::Closed) { HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, entry, db, version]() { fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), version); From 609f7aa65925294e6776ef6ddc106d8f4bee913c Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 21:12:35 +0100 Subject: [PATCH 169/237] LibWeb: Implement IDBFactory::delete_database --- Libraries/LibWeb/IndexedDB/IDBFactory.cpp | 55 +++++++++++++++++++++++ Libraries/LibWeb/IndexedDB/IDBFactory.h | 2 + Libraries/LibWeb/IndexedDB/IDBFactory.idl | 2 +- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp index 2c2ccbebe109e..f7314ddaacc4e 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp @@ -115,4 +115,59 @@ WebIDL::ExceptionOr IDBFactory::cmp(JS::Value first, JS::Value second) return Key::compare_two_keys(a.release_value(), b.release_value()); } +// https://w3c.github.io/IndexedDB/#dom-idbfactory-deletedatabase +WebIDL::ExceptionOr> IDBFactory::delete_database(String const& name) +{ + auto& realm = this->realm(); + + // 1. Let environment be this's relevant settings object. + auto& environment = HTML::relevant_settings_object(*this); + + // 2. Let storageKey be the result of running obtain a storage key given environment. + // If failure is returned, then throw a "SecurityError" DOMException and abort these steps. + auto storage_key = StorageAPI::obtain_a_storage_key(environment); + if (!storage_key.has_value()) + return WebIDL::SecurityError::create(realm, "Failed to obtain a storage key"_string); + + // 3. Let request be a new open request. + auto request = IDBOpenDBRequest::create(realm); + + // 4. Run these steps in parallel: + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, storage_key, name, request] { + HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + + // 1. Let result be the result of deleting a database, with storageKey, name, and request. + auto result = delete_a_database(realm, storage_key.value(), name, request); + + // 2. Set request’s processed flag to true. + request->set_processed(true); + + // 3. Queue a task to run these steps: + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.heap(), [&realm, request, result = move(result)]() mutable { + // 1. If result is an error, + if (result.is_error()) { + // set request’s error to result, + request->set_error(result.exception().get>()); + // set request’s done flag to true, + request->set_done(true); + // and fire an event named error at request with its bubbles and cancelable attributes initialized to true. + request->dispatch_event(DOM::Event::create(realm, HTML::EventNames::error, { .bubbles = true, .cancelable = true })); + } + // 2. Otherwise, + else { + // set request’s result to undefined, + request->set_result(JS::js_undefined()); + // set request’s done flag to true, + request->set_done(true); + // and fire a version change event named success at request with result and null. + auto value = result.release_value(); + fire_a_version_change_event(realm, HTML::EventNames::success, request, value, {}); + } + })); + })); + + // 5. Return a new IDBOpenDBRequest object for request. + return request; +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.h b/Libraries/LibWeb/IndexedDB/IDBFactory.h index b913672a1e2e3..ab7841b6e3af1 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.h +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.h @@ -21,6 +21,8 @@ class IDBFactory : public Bindings::PlatformObject { virtual ~IDBFactory() override; WebIDL::ExceptionOr> open(String const& name, Optional version); + WebIDL::ExceptionOr> delete_database(String const& name); + WebIDL::ExceptionOr cmp(JS::Value first, JS::Value second); protected: diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.idl b/Libraries/LibWeb/IndexedDB/IDBFactory.idl index 5f57f2afb3500..7edba81121d35 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.idl +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.idl @@ -2,7 +2,7 @@ [Exposed=(Window,Worker)] interface IDBFactory { [NewObject] IDBOpenDBRequest open(DOMString name, optional [EnforceRange] unsigned long long version); - [FIXME, NewObject] IDBOpenDBRequest deleteDatabase(DOMString name); + [NewObject] IDBOpenDBRequest deleteDatabase(DOMString name); [FIXME] Promise> databases(); From 2954278e37e6aec2ced0eb0bcc0dbac053b722ec Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 21:57:32 +0100 Subject: [PATCH 170/237] LibWeb: Implement abort_a_transaction for IndexedDB --- Libraries/LibWeb/IndexedDB/IDBRequest.h | 2 +- Libraries/LibWeb/IndexedDB/IDBTransaction.cpp | 1 + Libraries/LibWeb/IndexedDB/IDBTransaction.h | 4 ++ Libraries/LibWeb/IndexedDB/IDBTransaction.idl | 2 +- .../LibWeb/IndexedDB/Internal/Algorithms.cpp | 58 +++++++++++++++++++ .../LibWeb/IndexedDB/Internal/Algorithms.h | 1 + 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/IDBRequest.h b/Libraries/LibWeb/IndexedDB/IDBRequest.h index 10c16405504cd..7564c6e625306 100644 --- a/Libraries/LibWeb/IndexedDB/IDBRequest.h +++ b/Libraries/LibWeb/IndexedDB/IDBRequest.h @@ -38,7 +38,7 @@ class IDBRequest : public DOM::EventTarget { void set_error(GC::Ptr error) { m_error = error; } void set_processed(bool processed) { m_processed = processed; } void set_source(IDBRequestSource source) { m_source = source; } - void set_transaction(GC::Ref transaction) { m_transaction = transaction; } + void set_transaction(GC::Ptr transaction) { m_transaction = transaction; } void set_onsuccess(WebIDL::CallbackType*); WebIDL::CallbackType* onsuccess(); diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp index 61408832412a6..b546c7c8f9af5 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp @@ -36,6 +36,7 @@ void IDBTransaction::visit_edges(Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_connection); visitor.visit(m_error); + visitor.visit(m_associated_request); } void IDBTransaction::set_onabort(WebIDL::CallbackType* event_handler) diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.h b/Libraries/LibWeb/IndexedDB/IDBTransaction.h index 2528c05fe8540..1777935abfe05 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.h +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.h @@ -36,10 +36,12 @@ class IDBTransaction : public DOM::EventTarget { [[nodiscard]] GC::Ptr error() const { return m_error; } [[nodiscard]] GC::Ref connection() const { return m_connection; } [[nodiscard]] Bindings::IDBTransactionDurability durability() const { return m_durability; } + [[nodiscard]] GC::Ptr associated_request() const { return m_associated_request; } void set_mode(Bindings::IDBTransactionMode mode) { m_mode = mode; } void set_state(TransactionState state) { m_state = state; } void set_error(GC::Ptr error) { m_error = error; } + void set_associated_request(GC::Ptr request) { m_associated_request = request; } [[nodiscard]] bool is_upgrade_transaction() const { return m_mode == Bindings::IDBTransactionMode::Versionchange; } [[nodiscard]] bool is_readonly() const { return m_mode == Bindings::IDBTransactionMode::Readonly; } @@ -63,5 +65,7 @@ class IDBTransaction : public DOM::EventTarget { Bindings::IDBTransactionDurability m_durability { Bindings::IDBTransactionDurability::Default }; TransactionState m_state; GC::Ptr m_error; + + GC::Ptr m_associated_request; }; } diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.idl b/Libraries/LibWeb/IndexedDB/IDBTransaction.idl index f08d9a38d7aeb..bba6179e24035 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.idl +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.idl @@ -11,7 +11,7 @@ interface IDBTransaction : EventTarget { readonly attribute DOMException? error; [FIXME] IDBObjectStore objectStore(DOMString name); [FIXME] undefined commit(); - [FIXME] undefined abort(); + undefined abort(); attribute EventHandler onabort; attribute EventHandler oncomplete; diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 6c0a66a9043f0..ffb517fb05fa7 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -326,7 +326,9 @@ void upgrade_a_database(JS::Realm& realm, GC::Ref connection, u64 v request->set_result(connection); // 2. Set request’s transaction to transaction. + // NOTE: We need to do a two-way binding here. request->set_transaction(transaction); + transaction->set_associated_request(request); // 3. Set request’s done flag to true. request->set_done(true); @@ -422,4 +424,60 @@ WebIDL::ExceptionOr delete_a_database(JS::Realm& realm, StorageAPI::Storage return version; } +// https://w3c.github.io/IndexedDB/#abort-a-transaction +void abort_a_transaction(IDBTransaction& transaction, GC::Ptr error) +{ + // FIXME: 1. All the changes made to the database by the transaction are reverted. + // For upgrade transactions this includes changes to the set of object stores and indexes, as well as the change to the version. + // Any object stores and indexes which were created during the transaction are now considered deleted for the purposes of other algorithms. + + // FIXME: 2. If transaction is an upgrade transaction, run the steps to abort an upgrade transaction with transaction. + // if (transaction.is_upgrade_transaction()) + // abort_an_upgrade_transaction(transaction); + + // 3. Set transaction’s state to finished. + transaction.set_state(IDBTransaction::TransactionState::Finished); + + // 4. If error is not null, set transaction’s error to error. + if (error) + transaction.set_error(error); + + // FIXME: 5. For each request of transaction’s request list, abort the steps to asynchronously execute a request for request, + // set request’s processed flag to true, and queue a task to run these steps: + // FIXME: 1. Set request’s done flag to true. + // FIXME: 2. Set request’s result to undefined. + // FIXME: 3. Set request’s error to a newly created "AbortError" DOMException. + // FIXME: 4. Fire an event named error at request with its bubbles and cancelable attributes initialized to true. + + // 6. Queue a task to run these steps: + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(transaction.realm().vm().heap(), [&transaction]() { + // 1. If transaction is an upgrade transaction, then set transaction’s connection's associated database's upgrade transaction to null. + if (transaction.is_upgrade_transaction()) + transaction.connection()->associated_database()->set_upgrade_transaction(nullptr); + + // 2. Fire an event named abort at transaction with its bubbles attribute initialized to true. + transaction.dispatch_event(DOM::Event::create(transaction.realm(), HTML::EventNames::abort, { .bubbles = true })); + + // 3. If transaction is an upgrade transaction, then: + if (transaction.is_upgrade_transaction()) { + // 1. Let request be the open request associated with transaction. + auto request = transaction.associated_request(); + + // 2. Set request’s transaction to null. + // NOTE: Clear the two-way binding. + request->set_transaction(nullptr); + transaction.set_associated_request(nullptr); + + // 3. Set request’s result to undefined. + request->set_result(JS::js_undefined()); + + // 4. Set request’s processed flag to false. + request->set_processed(false); + + // 5. Set request’s done flag to false. + request->set_done(false); + } + })); +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index 806d1fe12c32d..ae235611ed4ae 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -19,5 +19,6 @@ ErrorOr convert_a_value_to_a_key(JS::Realm&, JS::Value, Vector = void close_a_database_connection(IDBDatabase&, bool forced = false); void upgrade_a_database(JS::Realm&, GC::Ref, u64, GC::Ref); WebIDL::ExceptionOr delete_a_database(JS::Realm&, StorageAPI::StorageKey, String, GC::Ref); +void abort_a_transaction(IDBTransaction&, GC::Ptr); } From 7c3f44282d52669d8ae4e4bec77a98dfb143927a Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 21:58:42 +0100 Subject: [PATCH 171/237] LibWeb: Implement IDBTransaction::abort --- Libraries/LibWeb/IndexedDB/IDBTransaction.cpp | 13 +++++++++++++ Libraries/LibWeb/IndexedDB/IDBTransaction.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp index b546c7c8f9af5..e58115f3edde8 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Web::IndexedDB { @@ -69,4 +70,16 @@ WebIDL::CallbackType* IDBTransaction::onerror() return event_handler_attribute(HTML::EventNames::error); } +WebIDL::ExceptionOr IDBTransaction::abort() +{ + // 1. If this's state is committing or finished, then throw an "InvalidStateError" DOMException. + if (m_state == TransactionState::Committing || m_state == TransactionState::Finished) + return WebIDL::InvalidStateError::create(realm(), "Transaction is ending"_string); + + // 2. Set this's state to inactive and run abort a transaction with this and null. + m_state = TransactionState::Inactive; + abort_a_transaction(*this, nullptr); + return {}; +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.h b/Libraries/LibWeb/IndexedDB/IDBTransaction.h index 1777935abfe05..d1fea580ca19f 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.h +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.h @@ -47,6 +47,8 @@ class IDBTransaction : public DOM::EventTarget { [[nodiscard]] bool is_readonly() const { return m_mode == Bindings::IDBTransactionMode::Readonly; } [[nodiscard]] bool is_readwrite() const { return m_mode == Bindings::IDBTransactionMode::Readwrite; } + WebIDL::ExceptionOr abort(); + void set_onabort(WebIDL::CallbackType*); WebIDL::CallbackType* onabort(); void set_oncomplete(WebIDL::CallbackType*); From 0b8f2a8b8159f12d67cb1a87aca459797c03ca8b Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 22:02:23 +0100 Subject: [PATCH 172/237] LibWeb: Implement wait step for IndexedDB upgrade_a_database --- Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index ffb517fb05fa7..f846f4058daf2 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -321,7 +321,8 @@ void upgrade_a_database(JS::Realm& realm, GC::Ref connection, u64 v request->set_processed(true); // 10. Queue a task to run these steps: - HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, request, connection, transaction, old_version, version]() { + IGNORE_USE_IN_ESCAPING_LAMBDA bool wait_for_transaction = true; + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, request, connection, transaction, old_version, version, &wait_for_transaction]() { // 1. Set request’s result to connection. request->set_result(connection); @@ -343,9 +344,14 @@ void upgrade_a_database(JS::Realm& realm, GC::Ref connection, u64 v transaction->set_state(IDBTransaction::TransactionState::Inactive); // FIXME: 7. If didThrow is true, run abort a transaction with transaction and a newly created "AbortError" DOMException. + + wait_for_transaction = false; })); - // FIXME: 11. Wait for transaction to finish. + // 11. Wait for transaction to finish. + HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&wait_for_transaction]() { + return !wait_for_transaction; + })); } // https://w3c.github.io/IndexedDB/#deleting-a-database From a25bba27faa0b32d7fde24f9b50e4c5fc8f727f3 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 22:52:33 +0100 Subject: [PATCH 173/237] LibWeb: Close the database if the upgrade connection is aborted --- Libraries/LibWeb/IndexedDB/IDBTransaction.h | 3 +++ Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp | 12 ++++++++++-- Libraries/LibWeb/IndexedDB/Internal/Database.h | 1 + .../wpt-import/IndexedDB/idbfactory_open.any.txt | 12 ++++++------ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.h b/Libraries/LibWeb/IndexedDB/IDBTransaction.h index d1fea580ca19f..14881a69dd06f 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.h +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.h @@ -37,11 +37,13 @@ class IDBTransaction : public DOM::EventTarget { [[nodiscard]] GC::Ref connection() const { return m_connection; } [[nodiscard]] Bindings::IDBTransactionDurability durability() const { return m_durability; } [[nodiscard]] GC::Ptr associated_request() const { return m_associated_request; } + [[nodiscard]] bool aborted() const { return m_aborted; } void set_mode(Bindings::IDBTransactionMode mode) { m_mode = mode; } void set_state(TransactionState state) { m_state = state; } void set_error(GC::Ptr error) { m_error = error; } void set_associated_request(GC::Ptr request) { m_associated_request = request; } + void set_aborted(bool aborted) { m_aborted = aborted; } [[nodiscard]] bool is_upgrade_transaction() const { return m_mode == Bindings::IDBTransactionMode::Versionchange; } [[nodiscard]] bool is_readonly() const { return m_mode == Bindings::IDBTransactionMode::Readonly; } @@ -69,5 +71,6 @@ class IDBTransaction : public DOM::EventTarget { GC::Ptr m_error; GC::Ptr m_associated_request; + bool m_aborted { false }; }; } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index f846f4058daf2..6e27eef42f9d9 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -126,8 +126,13 @@ WebIDL::ExceptionOr> open_a_database_connection(JS::Realm& return WebIDL::AbortError::create(realm, "Connection was closed"_string); } - // FIXME: 8. If the upgrade transaction was aborted, run the steps to close a database connection with connection, - // return a newly created "AbortError" DOMException and abort these steps. + // 8. If the upgrade transaction was aborted, run the steps to close a database connection with connection, + // return a newly created "AbortError" DOMException and abort these steps. + auto transaction = connection->associated_database()->upgrade_transaction(); + if (transaction->aborted()) { + close_a_database_connection(*connection, true); + return WebIDL::AbortError::create(realm, "Upgrade transaction was aborted"_string); + } } // 11. Return connection. @@ -433,6 +438,9 @@ WebIDL::ExceptionOr delete_a_database(JS::Realm& realm, StorageAPI::Storage // https://w3c.github.io/IndexedDB/#abort-a-transaction void abort_a_transaction(IDBTransaction& transaction, GC::Ptr error) { + // NOTE: This is not spec'ed anywhere, but we need to know IF the transaction was aborted. + transaction.set_aborted(true); + // FIXME: 1. All the changes made to the database by the transaction are reverted. // For upgrade transactions this includes changes to the set of object stores and indexes, as well as the change to the version. // Any object stores and indexes which were created during the transaction are now considered deleted for the purposes of other algorithms. diff --git a/Libraries/LibWeb/IndexedDB/Internal/Database.h b/Libraries/LibWeb/IndexedDB/Internal/Database.h index 8550eaa59acce..d5a150129ae87 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Database.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Database.h @@ -25,6 +25,7 @@ class Database : public Bindings::PlatformObject { String name() const { return m_name; } void set_upgrade_transaction(GC::Ptr transaction) { m_upgrade_transaction = transaction; } + [[nodiscard]] GC::Ptr upgrade_transaction() { return m_upgrade_transaction; } void associate(GC::Ref connection) { m_associated_connections.append(connection); } ReadonlySpan> associated_connections() { return m_associated_connections; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_open.any.txt b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_open.any.txt index fbdc931772c69..ea29667c7b6e4 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_open.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/IndexedDB/idbfactory_open.any.txt @@ -2,8 +2,8 @@ Harness status: OK Found 29 tests -23 Pass -6 Fail +27 Pass +2 Fail Pass IDBFactory.open() - request has no source Pass IDBFactory.open() - database 'name' and 'version' are correctly set Pass IDBFactory.open() - no version opens current database @@ -11,7 +11,7 @@ Pass IDBFactory.open() - new database has default version Pass IDBFactory.open() - new database is empty Pass IDBFactory.open() - open database with a lower version than current Pass IDBFactory.open() - open database with a higher version than current -Fail IDBFactory.open() - error in version change transaction aborts open +Pass IDBFactory.open() - error in version change transaction aborts open Pass Calling open() with version argument -1 should throw TypeError. Pass Calling open() with version argument -0.5 should throw TypeError. Pass Calling open() with version argument 0 should throw TypeError. @@ -27,9 +27,9 @@ Pass Calling open() with version argument false should throw TypeError. Pass Calling open() with version argument object should throw TypeError. Pass Calling open() with version argument object (second) should throw TypeError. Pass Calling open() with version argument object (third) should throw TypeError. -Fail Calling open() with version argument 1.5 should not throw. -Fail Calling open() with version argument 9007199254740991 should not throw. -Fail Calling open() with version argument undefined should not throw. +Pass Calling open() with version argument 1.5 should not throw. +Pass Calling open() with version argument 9007199254740991 should not throw. +Pass Calling open() with version argument undefined should not throw. Fail IDBFactory.open() - error in upgradeneeded resets db Fail IDBFactory.open() - second open's transaction is available to get objectStores Pass IDBFactory.open() - upgradeneeded gets VersionChangeEvent \ No newline at end of file From 32e5fb4da513108f91e713be45ecb69c1c14a61e Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sun, 1 Dec 2024 23:40:00 +0100 Subject: [PATCH 174/237] LibWeb: Add missing spec comment for method --- Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 6e27eef42f9d9..b080d285cc5b1 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -298,6 +298,7 @@ void close_a_database_connection(IDBDatabase& connection, bool forced) connection.dispatch_event(DOM::Event::create(connection.realm(), HTML::EventNames::close)); } +// https://w3c.github.io/IndexedDB/#upgrade-a-database void upgrade_a_database(JS::Realm& realm, GC::Ref connection, u64 version, GC::Ref request) { // 1. Let db be connection’s database. From 0b1c7d6af2c3ee828bd39afe5f070220700aabb1 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Fri, 13 Dec 2024 23:58:11 +0100 Subject: [PATCH 175/237] LibWeb: Mark open request as processed if the spec forgot --- Libraries/LibWeb/IndexedDB/IDBFactory.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp index f7314ddaacc4e..3955f8d0ee8ff 100644 --- a/Libraries/LibWeb/IndexedDB/IDBFactory.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBFactory.cpp @@ -62,6 +62,10 @@ WebIDL::ExceptionOr> IDBFactory::open(String const& na // 1. Let result be the result of opening a database connection, with storageKey, name, version if given and undefined otherwise, and request. auto result = open_a_database_connection(realm, storage_key.value(), name, version, request); + // FIXME: https://github.com/w3c/IndexedDB/issues/434 + // We need to set the request as processed, since we didnt always do it via the above function. + request->set_processed(true); + // 2. Queue a task to run these steps: HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.heap(), [&realm, request, result = move(result)]() mutable { // 1. If result is an error, then: From ea469fbeabe4946e674d278182fd5c68a25b7abc Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Tue, 3 Dec 2024 00:24:34 +0400 Subject: [PATCH 176/237] LibGfx: Let Painter care about TinyVG transforms This helps us with non-uniform scales, and makes things simple --- .../LibGfx/ImageFormats/TinyVGLoader.cpp | 16 ++++++-------- Libraries/LibGfx/ImageFormats/TinyVGLoader.h | 2 +- Libraries/LibGfx/VectorGraphic.cpp | 21 +++++++++---------- Libraries/LibGfx/VectorGraphic.h | 3 +-- UI/Qt/TVGIconEngine.cpp | 8 +------ 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp b/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp index 3b7a8933b2174..0abdeb8756bb0 100644 --- a/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp +++ b/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp @@ -471,29 +471,25 @@ ErrorOr> TinyVGDecodedImageData::decode(St return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) TinyVGDecodedImageData({ header.width, header.height }, move(draw_commands)))); } -void TinyVGDecodedImageData::draw_transformed(Painter& painter, AffineTransform transform) const +void TinyVGDecodedImageData::draw(Painter& painter) const { - // FIXME: Correctly handle non-uniform scales. - auto scale = max(transform.x_scale(), transform.y_scale()); for (auto const& command : draw_commands()) { - auto draw_path = command.path.copy_transformed(transform); + auto draw_path = command.path; if (command.fill.has_value()) { auto fill_path = draw_path; fill_path.close_all_subpaths(); command.fill->visit( [&](Color color) { painter.fill_path(fill_path, color, WindingRule::EvenOdd); }, - [&](NonnullRefPtr style) { - const_cast(*style).set_gradient_transform(transform); + [&](NonnullRefPtr const& style) { painter.fill_path(fill_path, style, 1.0f, WindingRule::EvenOdd); }); } if (command.stroke.has_value()) { command.stroke->visit( - [&](Color color) { painter.stroke_path(draw_path, color, command.stroke_width * scale); }, - [&](NonnullRefPtr style) { - const_cast(*style).set_gradient_transform(transform); - painter.stroke_path(draw_path, style, command.stroke_width * scale, 1.0f); + [&](Color color) { painter.stroke_path(draw_path, color, command.stroke_width); }, + [&](NonnullRefPtr const& style) { + painter.stroke_path(draw_path, style, command.stroke_width, 1.0f); }); } } diff --git a/Libraries/LibGfx/ImageFormats/TinyVGLoader.h b/Libraries/LibGfx/ImageFormats/TinyVGLoader.h index dff2af12f7501..6f7864b7710b1 100644 --- a/Libraries/LibGfx/ImageFormats/TinyVGLoader.h +++ b/Libraries/LibGfx/ImageFormats/TinyVGLoader.h @@ -53,7 +53,7 @@ class TinyVGDecodedImageData final : public VectorGraphic { return m_size; } - virtual void draw_transformed(Painter&, AffineTransform) const override; + virtual void draw(Painter&) const override; ReadonlySpan draw_commands() const { diff --git a/Libraries/LibGfx/VectorGraphic.cpp b/Libraries/LibGfx/VectorGraphic.cpp index 255c55f44ad1a..0f3f7bee5352f 100644 --- a/Libraries/LibGfx/VectorGraphic.cpp +++ b/Libraries/LibGfx/VectorGraphic.cpp @@ -9,26 +9,25 @@ namespace Gfx { -void VectorGraphic::draw_into(Painter& painter, IntRect const& dest, AffineTransform transform) const +ErrorOr> VectorGraphic::bitmap(IntSize size, AffineTransform transform) const { + auto bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::BGRA8888, size)); + auto painter = PainterSkia::create(bitmap); + // Apply the transform then center within destination rectangle (this ignores any translation from the transform): // This allows you to easily rotate or flip the image before painting. - auto transformed_rect = transform.map(FloatRect { {}, size() }); - auto scale = min(float(dest.width()) / transformed_rect.width(), float(dest.height()) / transformed_rect.height()); - auto centered = FloatRect { {}, transformed_rect.size().scaled(scale) }.centered_within(dest.to_type()); + auto transformed_rect = transform.map(FloatRect { {}, this->size() }); + auto scale = min(float(size.width()) / transformed_rect.width(), float(size.height()) / transformed_rect.height()); + auto centered = FloatRect { {}, transformed_rect.size().scaled(scale) }.centered_within(IntRect { {}, size }.to_type()); auto view_transform = AffineTransform {} .translate(centered.location()) .multiply(AffineTransform {}.scale(scale, scale)) .multiply(AffineTransform {}.translate(-transformed_rect.location())) .multiply(transform); - draw_transformed(painter, view_transform); -} -ErrorOr> VectorGraphic::bitmap(IntSize size, AffineTransform transform) const -{ - auto bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::BGRA8888, size)); - auto painter = PainterSkia::create(bitmap); - draw_into(*painter, IntRect { {}, size }, transform); + painter->set_transform(view_transform); + draw(*painter); + return bitmap; } diff --git a/Libraries/LibGfx/VectorGraphic.h b/Libraries/LibGfx/VectorGraphic.h index 6f048b4dcec2c..73ad93ae250a9 100644 --- a/Libraries/LibGfx/VectorGraphic.h +++ b/Libraries/LibGfx/VectorGraphic.h @@ -17,13 +17,12 @@ namespace Gfx { class VectorGraphic : public RefCounted { public: virtual IntSize intrinsic_size() const = 0; - virtual void draw_transformed(Painter&, AffineTransform) const = 0; + virtual void draw(Painter&) const = 0; IntSize size() const { return intrinsic_size(); } IntRect rect() const { return { {}, size() }; } ErrorOr> bitmap(IntSize size, AffineTransform = {}) const; - void draw_into(Painter& painter, IntRect const& dest, AffineTransform = {}) const; virtual ~VectorGraphic() = default; }; diff --git a/UI/Qt/TVGIconEngine.cpp b/UI/Qt/TVGIconEngine.cpp index 862291bcbc8b6..9062b80dd0c72 100644 --- a/UI/Qt/TVGIconEngine.cpp +++ b/UI/Qt/TVGIconEngine.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -34,12 +33,7 @@ QPixmap TVGIconEngine::pixmap(QSize const& size, QIcon::Mode mode, QIcon::State auto key = pixmap_cache_key(size, mode, state); if (QPixmapCache::find(key, &pixmap)) return pixmap; - auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { size.width(), size.height() })); - - auto painter = Gfx::PainterSkia::create(bitmap); - painter->clear_rect(bitmap->rect().to_type(), Gfx::Color::Transparent); - - m_image_data->draw_into(*painter, bitmap->rect()); + auto bitmap = MUST(m_image_data->bitmap({ size.width(), size.height() })); for (auto const& filter : m_filters) { if (filter->mode() == mode) { From 8ac5424f3a30f4285cff936b57b74a43a20d71d5 Mon Sep 17 00:00:00 2001 From: stasoid Date: Sat, 14 Dec 2024 15:10:42 +0500 Subject: [PATCH 177/237] LibTLS: Port to Windows --- Libraries/LibTLS/CMakeLists.txt | 5 +++++ Libraries/LibTLS/TLSv12.cpp | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Libraries/LibTLS/CMakeLists.txt b/Libraries/LibTLS/CMakeLists.txt index f21dc89a92273..c9883dc92dcd2 100644 --- a/Libraries/LibTLS/CMakeLists.txt +++ b/Libraries/LibTLS/CMakeLists.txt @@ -14,3 +14,8 @@ serenity_lib(LibTLS tls) target_link_libraries(LibTLS PRIVATE LibCore LibCrypto LibFileSystem) include(ca_certificates_data) + +if (WIN32) + find_package(pthread REQUIRED) + target_include_directories(LibTLS PRIVATE ${PTHREAD_INCLUDE_DIR}) +endif() diff --git a/Libraries/LibTLS/TLSv12.cpp b/Libraries/LibTLS/TLSv12.cpp index 34682150ef67e..63ffb656e08b7 100644 --- a/Libraries/LibTLS/TLSv12.cpp +++ b/Libraries/LibTLS/TLSv12.cpp @@ -23,10 +23,6 @@ #include #include -#ifndef SOCK_NONBLOCK -# include -#endif - namespace TLS { void TLSv12::consume(ReadonlyBytes record) From 9c76260756865011331342146410c8feb6b53b4c Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Fri, 13 Dec 2024 16:55:57 -0800 Subject: [PATCH 178/237] LibWeb: Sync statusText check with spec update Synced change from https://github.com/whatwg/fetch/pull/1795. No functional changes; only update comments to reflect the latest spec. --- Libraries/LibWeb/Fetch/Response.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Fetch/Response.cpp b/Libraries/LibWeb/Fetch/Response.cpp index 6d3c133621267..6eac1a90e2800 100644 --- a/Libraries/LibWeb/Fetch/Response.cpp +++ b/Libraries/LibWeb/Fetch/Response.cpp @@ -87,7 +87,7 @@ GC::Ref Response::create(JS::Realm& realm, GC::Ref Response::initialize_response(ResponseInit const& init if (init.status < 200 || init.status > 599) return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be in range 200-599"sv }; - // 2. If init["statusText"] does not match the reason-phrase token production, then throw a TypeError. + // 2. If init["statusText"] is not the empty string and does not match the reason-phrase token production, then throw a TypeError. if (!is_valid_status_text(init.status_text)) return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid statusText: does not match the reason-phrase token production"sv }; From 7bb7edd6dfc8c10c56087844ae2cad14e5f6f622 Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Fri, 13 Dec 2024 16:48:38 -0800 Subject: [PATCH 179/237] LibWeb: Fix `is_valid_status_text` to handle UTF-8 correctly This change fixed a WPT subtest which I also imported. --- Libraries/LibWeb/Fetch/Response.cpp | 4 +- .../api/response/response-init-001.any.txt | 14 ++++ .../api/response/response-init-001.any.html | 15 +++++ .../api/response/response-init-001.any.js | 64 +++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-init-001.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.js diff --git a/Libraries/LibWeb/Fetch/Response.cpp b/Libraries/LibWeb/Fetch/Response.cpp index 6eac1a90e2800..51cb93470857c 100644 --- a/Libraries/LibWeb/Fetch/Response.cpp +++ b/Libraries/LibWeb/Fetch/Response.cpp @@ -85,13 +85,13 @@ GC::Ref Response::create(JS::Realm& realm, GC::Ref= 0x21 && c <= 0x7E) || (c >= 0x80 && c <= 0xFF); }); } diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-init-001.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-init-001.any.txt new file mode 100644 index 0000000000000..2eee99c4ad4b9 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/response/response-init-001.any.txt @@ -0,0 +1,14 @@ +Harness status: OK + +Found 9 tests + +9 Pass +Pass Check default value for type attribute +Pass Check default value for url attribute +Pass Check default value for ok attribute +Pass Check default value for status attribute +Pass Check default value for statusText attribute +Pass Check default value for body attribute +Pass Check status init values and associated getter +Pass Check statusText init values and associated getter +Pass Test that Response.headers has the [SameObject] extended attribute \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.html new file mode 100644 index 0000000000000..25792d9897635 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.html @@ -0,0 +1,15 @@ + + +Response init: simple cases + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.js new file mode 100644 index 0000000000000..559e49ad11ffe --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/response/response-init-001.any.js @@ -0,0 +1,64 @@ +// META: global=window,worker +// META: title=Response init: simple cases + +var defaultValues = { "type" : "default", + "url" : "", + "ok" : true, + "status" : 200, + "statusText" : "", + "body" : null +}; + +var statusCodes = { "givenValues" : [200, 300, 400, 500, 599], + "expectedValues" : [200, 300, 400, 500, 599] +}; +var statusTexts = { "givenValues" : ["", "OK", "with space", String.fromCharCode(0x80)], + "expectedValues" : ["", "OK", "with space", String.fromCharCode(0x80)] +}; +var initValuesDict = { "status" : statusCodes, + "statusText" : statusTexts +}; + +function isOkStatus(status) { + return 200 <= status && 299 >= status; +} + +var response = new Response(); +for (var attributeName in defaultValues) { + test(function() { + var expectedValue = defaultValues[attributeName]; + assert_equals(response[attributeName], expectedValue, + "Expect default response." + attributeName + " is " + expectedValue); + }, "Check default value for " + attributeName + " attribute"); +} + +for (var attributeName in initValuesDict) { + test(function() { + var valuesToTest = initValuesDict[attributeName]; + for (var valueIdx in valuesToTest["givenValues"]) { + var givenValue = valuesToTest["givenValues"][valueIdx]; + var expectedValue = valuesToTest["expectedValues"][valueIdx]; + var responseInit = {}; + responseInit[attributeName] = givenValue; + var response = new Response("", responseInit); + assert_equals(response[attributeName], expectedValue, + "Expect response." + attributeName + " is " + expectedValue + + " when initialized with " + givenValue); + assert_equals(response.ok, isOkStatus(response.status), + "Expect response.ok is " + isOkStatus(response.status)); + } + }, "Check " + attributeName + " init values and associated getter"); +} + +test(function() { + const response1 = new Response(""); + assert_equals(response1.headers, response1.headers); + + const response2 = new Response("", {"headers": {"X-Foo": "bar"}}); + assert_equals(response2.headers, response2.headers); + const headers = response2.headers; + response2.headers.set("X-Foo", "quux"); + assert_equals(headers, response2.headers); + headers.set("X-Other-Header", "baz"); + assert_equals(headers, response2.headers); +}, "Test that Response.headers has the [SameObject] extended attribute"); From 108701c8997e8484890adee8dbf1d4cb81a51c39 Mon Sep 17 00:00:00 2001 From: Gustavo Ramirez Date: Wed, 11 Sep 2024 09:01:17 -0500 Subject: [PATCH 180/237] LibWeb/CSS: Refactor phase() method to reduce redundancy The function AnimationEffect::phase() contained duplicated condition checks for the animation phase determination. This refactor eliminates the redundant checks by simplifying the logic. --- .../LibWeb/Animations/AnimationEffect.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Libraries/LibWeb/Animations/AnimationEffect.cpp b/Libraries/LibWeb/Animations/AnimationEffect.cpp index 5af94e3598543..4c97a850fa3ee 100644 --- a/Libraries/LibWeb/Animations/AnimationEffect.cpp +++ b/Libraries/LibWeb/Animations/AnimationEffect.cpp @@ -424,15 +424,25 @@ AnimationEffect::Phase AnimationEffect::phase() const { // This is a convenience method that returns the phase of the animation effect, to avoid having to call all of the // phase functions separately. - // FIXME: There is a lot of duplicated condition checking here which can probably be inlined into this function + auto local_time = this->local_time(); + if (!local_time.has_value()) + return Phase::Idle; - if (is_in_the_before_phase()) + auto before_active_boundary_time = this->before_active_boundary_time(); + // - the local time is less than the before-active boundary time, or + // - the animation direction is "backwards" and the local time is equal to the before-active boundary time. + if (local_time.value() < before_active_boundary_time || (animation_direction() == AnimationDirection::Backwards && local_time.value() == before_active_boundary_time)) return Phase::Before; - if (is_in_the_active_phase()) - return Phase::Active; - if (is_in_the_after_phase()) + + auto after_active_boundary_time = this->after_active_boundary_time(); + // - the local time is greater than the active-after boundary time, or + // - the animation direction is "forwards" and the local time is equal to the active-after boundary time. + if (local_time.value() > after_active_boundary_time || (animation_direction() == AnimationDirection::Forwards && local_time.value() == after_active_boundary_time)) return Phase::After; - return Phase::Idle; + + // - An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not + // - in either the before phase nor the after phase. + return Phase::Active; } // https://www.w3.org/TR/web-animations-1/#overall-progress From 5c4e162ba968dfd9b398fd71781d9cf214aa1f48 Mon Sep 17 00:00:00 2001 From: Aaron Van Doren Date: Fri, 2 Aug 2024 19:21:34 -0700 Subject: [PATCH 181/237] LibGfx: Implement even-odd method for Quad::contains() and add tests --- Libraries/LibGfx/Quad.h | 47 +++++++++++++++++------ Tests/LibGfx/CMakeLists.txt | 1 + Tests/LibGfx/TestQuad.cpp | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 Tests/LibGfx/TestQuad.cpp diff --git a/Libraries/LibGfx/Quad.h b/Libraries/LibGfx/Quad.h index 21a5ae182342b..2cfbb0fcc562a 100644 --- a/Libraries/LibGfx/Quad.h +++ b/Libraries/LibGfx/Quad.h @@ -6,8 +6,8 @@ #pragma once +#include #include -#include namespace Gfx { @@ -29,20 +29,45 @@ class Quad { Rect bounding_rect() const { - auto top = min(min(m_p1.y(), m_p2.y()), min(m_p3.y(), m_p4.y())); - auto right = max(max(m_p1.x(), m_p2.x()), max(m_p3.x(), m_p4.x())); - auto bottom = max(max(m_p1.y(), m_p2.y()), max(m_p3.y(), m_p4.y())); - auto left = min(min(m_p1.x(), m_p2.x()), min(m_p3.x(), m_p4.x())); - return { left, top, right - left, bottom - top }; + T left = min(min(m_p1.x(), m_p2.x()), min(m_p3.x(), m_p4.x())); + T right = max(max(m_p1.x(), m_p2.x()), max(m_p3.x(), m_p4.x())); + T width = right - left; + + T top = min(min(m_p1.y(), m_p2.y()), min(m_p3.y(), m_p4.y())); + T bottom = max(max(m_p1.y(), m_p2.y()), max(m_p3.y(), m_p4.y())); + T height = bottom - top; + + return { left, top, width, height }; } bool contains(Point point) const { - // FIXME: There's probably a smarter way to do this. - return Triangle(m_p1, m_p2, m_p3).contains(point) - || Triangle(m_p1, m_p3, m_p4).contains(point) - || Triangle(m_p2, m_p4, m_p1).contains(point) - || Triangle(m_p2, m_p4, m_p3).contains(point); + // Even-Odd algorithm: https://www.geeksforgeeks.org/even-odd-method-winding-number-method-inside-outside-test-of-a-polygon/ + // + // 1. "Constructing a line segment between the point (P) to be examined and a known point outside the polygon" + // - We're using horizontal line from (point.x, point.y) to (bounding_rect().left + bounding_rect().width + 1, point.y) + // (i.e. just +1 to right of furthest-right point in quad) + // + // 2. "The number of times the line segment intersects the polygon boundary is then counted." + // - We count the line's intersections with the quad by checking each quad edge for intersection (1-2, 2-3, 3-4, 4-1) + // + // 3. "The point (P) is an internal point if the number of polygon edges intersected by this line is odd; + // otherwise, the point is an external point." + + u8 intersections_with_quad = 0; + auto const quad_points = AK::Array { &m_p1, &m_p2, &m_p3, &m_p4, &m_p1 }; + for (size_t i = 0, j = 1; i < 4 && j < 5; i++, j++) { + if ((quad_points[i]->y() > point.y()) == (quad_points[j]->y() > point.y())) { + continue; + } + + T x_coord_of_intersection_with_edge = (quad_points[j]->x() - quad_points[i]->x()) * (point.y() - quad_points[i]->y()) / (quad_points[j]->y() - quad_points[i]->y()) + quad_points[i]->x(); + if (point.x() < x_coord_of_intersection_with_edge) { + intersections_with_quad++; + } + } + + return (intersections_with_quad % 2) == 1; } private: diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index 4c15de43c1c92..2666d735d968d 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -5,6 +5,7 @@ set(TEST_SOURCES TestICCProfile.cpp TestImageDecoder.cpp TestImageWriter.cpp + TestQuad.cpp TestRect.cpp TestWOFF.cpp TestWOFF2.cpp diff --git a/Tests/LibGfx/TestQuad.cpp b/Tests/LibGfx/TestQuad.cpp new file mode 100644 index 0000000000000..9b0c4a8f0b4bc --- /dev/null +++ b/Tests/LibGfx/TestQuad.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Aaron Van Doren + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +TEST_CASE(quad_points) +{ + uint8_t quad_x_left = 1; + uint8_t quad_x_right = 5; + uint8_t quad_y_top = 10; + uint8_t quad_y_bottom = 6; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + + Gfx::Quad quad { left_bottom, left_top, right_bottom, right_top }; + EXPECT_EQ(quad.p1(), left_bottom); + EXPECT_EQ(quad.p2(), left_top); + EXPECT_EQ(quad.p3(), right_bottom); + EXPECT_EQ(quad.p4(), right_top); +} + +TEST_CASE(quad_bounding_rect) +{ + uint8_t quad_width = 5; + uint8_t quad_height = 4; + uint8_t quad_x_left = 0; + uint8_t quad_y_top = 6; + + uint8_t quad_x_right = quad_x_left + quad_width; + uint8_t quad_y_bottom = quad_y_top + quad_height; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + Gfx::Quad quad = { left_bottom, left_top, right_top, right_bottom }; + + auto bounding_rect = quad.bounding_rect(); + EXPECT_EQ(bounding_rect.x(), quad_x_left); + EXPECT_EQ(bounding_rect.y(), quad_y_top); + EXPECT_EQ(bounding_rect.width(), quad_width); + EXPECT_EQ(bounding_rect.height(), quad_height); +} + +TEST_CASE(quad_contains) +{ + u8 quad_width = 5; + u8 quad_height = 4; + u8 quad_x_left = 0; + u8 quad_y_top = 6; + + u8 quad_x_right = quad_x_left + quad_width; + u8 quad_y_bottom = quad_y_top + quad_height; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + Gfx::Quad quad = { left_bottom, left_top, right_top, right_bottom }; + + Gfx::Point in_bounds_point { 1, 7 }; + EXPECT(quad.contains(in_bounds_point) == true); + + Gfx::Point out_bounds_point { 7, 12 }; + EXPECT(quad.contains(out_bounds_point) == false); +} From 78247211076e653d5b0a16b7031e530dcf798529 Mon Sep 17 00:00:00 2001 From: Aaron Van Doren Date: Wed, 7 Aug 2024 17:32:12 -0700 Subject: [PATCH 182/237] LibGfx: Verify Rect and Quad are consistent in boundary point inclusions --- Tests/LibGfx/TestQuad.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Tests/LibGfx/TestQuad.cpp b/Tests/LibGfx/TestQuad.cpp index 9b0c4a8f0b4bc..04160ff075ab5 100644 --- a/Tests/LibGfx/TestQuad.cpp +++ b/Tests/LibGfx/TestQuad.cpp @@ -73,3 +73,33 @@ TEST_CASE(quad_contains) Gfx::Point out_bounds_point { 7, 12 }; EXPECT(quad.contains(out_bounds_point) == false); } + +TEST_CASE(quad_contains_boundary_points) +{ + Gfx::Point p1 { 0, 0 }; + Gfx::Point p2 { 2, 0 }; + Gfx::Point p3 { 2, 2 }; + Gfx::Point p4 { 0, 2 }; + Gfx::Quad square_quad { p1, p2, p3, p4 }; + Gfx::Rect square_quad_as_rect = square_quad.bounding_rect(); + + auto const& point_at_top_left_vertex = p1; + Gfx::Point point_on_top_edge { 1, 0 }; + EXPECT_EQ(square_quad.contains(point_at_top_left_vertex), square_quad_as_rect.contains(point_at_top_left_vertex)); + EXPECT_EQ(square_quad.contains(point_on_top_edge), square_quad_as_rect.contains(point_on_top_edge)); + + auto const& point_at_top_right_vertex = p2; + Gfx::Point point_on_right_edge { 2, 1 }; + EXPECT_EQ(square_quad.contains(point_at_top_right_vertex), square_quad_as_rect.contains(point_at_top_right_vertex)); + EXPECT_EQ(square_quad.contains(point_on_right_edge), square_quad_as_rect.contains(point_on_right_edge)); + + auto const& point_at_bottom_left_vertex = p4; + Gfx::Point point_on_bottom_edge { 1, 2 }; + EXPECT_EQ(square_quad.contains(point_at_bottom_left_vertex), square_quad_as_rect.contains(point_at_bottom_left_vertex)); + EXPECT_EQ(square_quad.contains(point_on_bottom_edge), square_quad_as_rect.contains(point_on_bottom_edge)); + + auto const& point_at_bottom_right_vertex = p3; + Gfx::Point point_on_left_edge { 0, 1 }; + EXPECT_EQ(square_quad.contains(point_at_bottom_right_vertex), square_quad_as_rect.contains(point_at_bottom_right_vertex)); + EXPECT_EQ(square_quad.contains(point_on_left_edge), square_quad_as_rect.contains(point_on_left_edge)); +} From 40bf8dde886c4e2f1b54a6c45a1258298ae3c8ef Mon Sep 17 00:00:00 2001 From: Pavel Shliak Date: Sun, 15 Dec 2024 13:56:24 +0400 Subject: [PATCH 183/237] LibWeb: Remove unnecessary Quad.h include --- Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 6acd6c7c0549a..4c48a68d5686b 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include From a74ef5df3df19a12366ff4fbf0a7b45dea5d19c3 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 16:02:51 +0100 Subject: [PATCH 184/237] LibCrypto: Reset cached trimmed length after `add_into_accumulator` The trimmed cache length of the `UnsignedBigInteger` was not reset after an `add_into_accumulator_without_allocation` operation because the function manipulates the words directly. This meant that if the trimmed length was calculated before this operation it would be wrong after. --- Libraries/LibCrypto/BigInt/Algorithms/SimpleOperations.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/LibCrypto/BigInt/Algorithms/SimpleOperations.cpp b/Libraries/LibCrypto/BigInt/Algorithms/SimpleOperations.cpp index 78ac298c61473..3c3302121aa7d 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/SimpleOperations.cpp +++ b/Libraries/LibCrypto/BigInt/Algorithms/SimpleOperations.cpp @@ -66,6 +66,8 @@ void UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(Unsig // Note : The accumulator couldn't add the carry directly, so we reached its end accumulator.m_words.append(last_carry_for_word); } + + accumulator.m_cached_trimmed_length = {}; } /** From b35764da0e5c5a2f12dee6f09e3a93f95a567f3a Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 16:04:20 +0100 Subject: [PATCH 185/237] LibCrypto: Add extended GCD algorithm --- Libraries/LibCrypto/BigInt/Algorithms/GCD.cpp | 57 +++++++++++++++++++ .../Algorithms/UnsignedBigIntegerAlgorithms.h | 1 + 2 files changed, 58 insertions(+) diff --git a/Libraries/LibCrypto/BigInt/Algorithms/GCD.cpp b/Libraries/LibCrypto/BigInt/Algorithms/GCD.cpp index 9b9e3e867b471..173dac992498d 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/GCD.cpp +++ b/Libraries/LibCrypto/BigInt/Algorithms/GCD.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Ali Mohammad Pur * Copyright (c) 2020-2021, Dex♪ + * Copyright (c) 2024, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -36,4 +37,60 @@ void UnsignedBigIntegerAlgorithms::destructive_GCD_without_allocation( } } +void UnsignedBigIntegerAlgorithms::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) +{ + gcd.set_to(a); + x.set_to(1); + y.set_to(0); + + temp_r.set_to(b); + temp_s.set_to_0(); + temp_t.set_to(1); + + while (temp_r != 0) { + // quotient := old_r div r + divide_without_allocation(gcd, temp_r, temp_quotient, temp_1); + + temp_2.set_to(temp_r); + multiply_without_allocation(temp_quotient, temp_r, temp_shift_result, temp_shift_plus, temp_shift, temp_1); + while (gcd < temp_1) { + add_into_accumulator_without_allocation(gcd, b); + } + subtract_without_allocation(gcd, temp_1, temp_r); + gcd.set_to(temp_2); + + // (old_s, s) := (s, old_s − quotient × s) + temp_2.set_to(temp_s); + multiply_without_allocation(temp_quotient, temp_s, temp_shift_result, temp_shift_plus, temp_shift, temp_1); + while (x < temp_1) { + add_into_accumulator_without_allocation(x, b); + } + subtract_without_allocation(x, temp_1, temp_s); + x.set_to(temp_2); + + // (old_t, t) := (t, old_t − quotient × t) + temp_2.set_to(temp_t); + multiply_without_allocation(temp_quotient, temp_t, temp_shift_result, temp_shift_plus, temp_shift, temp_1); + while (y < temp_1) { + add_into_accumulator_without_allocation(y, b); + } + subtract_without_allocation(y, temp_1, temp_t); + y.set_to(temp_2); + } +} + } diff --git a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h index 53c89118681e5..2b8b80aaf0cd5 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h +++ b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h @@ -27,6 +27,7 @@ class UnsignedBigIntegerAlgorithms { 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 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); static void destructive_GCD_without_allocation(UnsignedBigInteger& temp_a, UnsignedBigInteger& temp_b, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& output); static void modular_inverse_without_allocation(UnsignedBigInteger const& a_, UnsignedBigInteger const& b, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_minus, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_d, UnsignedBigInteger& temp_u, UnsignedBigInteger& temp_v, UnsignedBigInteger& temp_x, UnsignedBigInteger& result); static void destructive_modular_power_without_allocation(UnsignedBigInteger& ep, UnsignedBigInteger& base, UnsignedBigInteger const& m, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_3, UnsignedBigInteger& temp_multiply, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& result); From f49a55d0893539040428ce2f543b8d301bb6b114 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 16:06:14 +0100 Subject: [PATCH 186/237] LibCrypto: Update `ModularInverse` implementation to use extended GCD The previous implementation of `ModularInverse` was flaky and did not compute the correct value in many occasions, especially with big numbers like in RSA. Also added a bunch of tests with big numbers. --- .../BigInt/Algorithms/ModularInverse.cpp | 86 ++++--------------- .../Algorithms/UnsignedBigIntegerAlgorithms.h | 2 +- .../NumberTheory/ModularFunctions.cpp | 23 +++-- Tests/LibCrypto/TestBigInteger.cpp | 34 ++++++++ 4 files changed, 65 insertions(+), 80 deletions(-) diff --git a/Libraries/LibCrypto/BigInt/Algorithms/ModularInverse.cpp b/Libraries/LibCrypto/BigInt/Algorithms/ModularInverse.cpp index 22e4350c13993..fff701eef2eb8 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/ModularInverse.cpp +++ b/Libraries/LibCrypto/BigInt/Algorithms/ModularInverse.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Ali Mohammad Pur * Copyright (c) 2020-2021, Dex♪ + * Copyright (c) 2024, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,79 +13,24 @@ namespace Crypto { void UnsignedBigIntegerAlgorithms::modular_inverse_without_allocation( UnsignedBigInteger const& a, UnsignedBigInteger const& b, - UnsignedBigInteger& temp_1, - UnsignedBigInteger& temp_minus, + UnsignedBigInteger& result, + UnsignedBigInteger& temp_y, + UnsignedBigInteger& temp_gcd, UnsignedBigInteger& temp_quotient, - UnsignedBigInteger& temp_d, - UnsignedBigInteger& temp_u, - UnsignedBigInteger& temp_v, - UnsignedBigInteger& temp_x, - UnsignedBigInteger& result) + 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) { - UnsignedBigInteger one { 1 }; - - temp_u.set_to(a); - if (!a.is_odd()) { - // u += b - add_into_accumulator_without_allocation(temp_u, b); - } - - temp_v.set_to(b); - temp_x.set_to(0); - - // d = b - 1 - subtract_without_allocation(b, one, temp_d); - - while (!(temp_v == 1)) { - while (temp_v < temp_u) { - // u -= v - subtract_without_allocation(temp_u, temp_v, temp_minus); - temp_u.set_to(temp_minus); - - // d += x - add_into_accumulator_without_allocation(temp_d, temp_x); - - while (!temp_u.is_odd()) { - if (temp_d.is_odd()) { - // d += b - add_into_accumulator_without_allocation(temp_d, b); - } - - // u /= 2 - divide_u16_without_allocation(temp_u, 2, temp_quotient, temp_1); - temp_u.set_to(temp_quotient); - - // d /= 2 - divide_u16_without_allocation(temp_d, 2, temp_quotient, temp_1); - temp_d.set_to(temp_quotient); - } - } - - // v -= u - subtract_without_allocation(temp_v, temp_u, temp_minus); - temp_v.set_to(temp_minus); - - // x += d - add_into_accumulator_without_allocation(temp_x, temp_d); - - while (!temp_v.is_odd()) { - if (temp_x.is_odd()) { - // x += b - add_into_accumulator_without_allocation(temp_x, b); - } - - // v /= 2 - divide_u16_without_allocation(temp_v, 2, temp_quotient, temp_1); - temp_v.set_to(temp_quotient); - - // x /= 2 - divide_u16_without_allocation(temp_x, 2, temp_quotient, temp_1); - temp_x.set_to(temp_quotient); - } - } + extended_GCD_without_allocation(a, b, result, temp_y, temp_gcd, temp_quotient, temp_1, temp_2, temp_shift_result, temp_shift_plus, temp_shift, temp_r, temp_s, temp_t); - // return x % b - divide_without_allocation(temp_x, b, temp_quotient, result); + divide_without_allocation(result, b, temp_quotient, temp_1); + add_into_accumulator_without_allocation(temp_1, b); + divide_without_allocation(temp_1, b, temp_quotient, result); } } diff --git a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h index 2b8b80aaf0cd5..d40961b89a2dd 100644 --- a/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h +++ b/Libraries/LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h @@ -29,7 +29,7 @@ class UnsignedBigIntegerAlgorithms { 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); static void destructive_GCD_without_allocation(UnsignedBigInteger& temp_a, UnsignedBigInteger& temp_b, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& output); - static void modular_inverse_without_allocation(UnsignedBigInteger const& a_, UnsignedBigInteger const& b, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_minus, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_d, UnsignedBigInteger& temp_u, UnsignedBigInteger& temp_v, UnsignedBigInteger& temp_x, UnsignedBigInteger& result); + static void modular_inverse_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& result, UnsignedBigInteger& temp_y, UnsignedBigInteger& temp_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); static void destructive_modular_power_without_allocation(UnsignedBigInteger& ep, UnsignedBigInteger& base, UnsignedBigInteger const& m, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_3, UnsignedBigInteger& temp_multiply, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& result); static void montgomery_modular_power_with_minimal_allocations(UnsignedBigInteger const& base, UnsignedBigInteger const& exponent, UnsignedBigInteger const& modulo, UnsignedBigInteger& temp_z0, UnsignedBigInteger& temp_rr, UnsignedBigInteger& temp_one, UnsignedBigInteger& temp_z, UnsignedBigInteger& temp_zz, UnsignedBigInteger& temp_x, UnsignedBigInteger& temp_extra, UnsignedBigInteger& result); diff --git a/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp b/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp index 6f466d396ad86..cef97c305fca3 100644 --- a/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp +++ b/Libraries/LibCrypto/NumberTheory/ModularFunctions.cpp @@ -19,21 +19,26 @@ UnsignedBigInteger Mod(UnsignedBigInteger const& a, UnsignedBigInteger const& b) return result; } -UnsignedBigInteger ModularInverse(UnsignedBigInteger const& a_, UnsignedBigInteger const& b) +UnsignedBigInteger ModularInverse(UnsignedBigInteger const& a, UnsignedBigInteger const& b) { if (b == 1) return { 1 }; - UnsignedBigInteger temp_1; - UnsignedBigInteger temp_minus; - UnsignedBigInteger temp_quotient; - UnsignedBigInteger temp_d; - UnsignedBigInteger temp_u; - UnsignedBigInteger temp_v; - UnsignedBigInteger temp_x; UnsignedBigInteger result; + UnsignedBigInteger temp_y; + UnsignedBigInteger temp_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; + + UnsignedBigIntegerAlgorithms::modular_inverse_without_allocation(a, b, result, temp_y, temp_gcd, temp_quotient, temp_1, temp_2, temp_shift_result, temp_shift_plus, temp_shift, temp_r, temp_s, temp_t); - UnsignedBigIntegerAlgorithms::modular_inverse_without_allocation(a_, b, temp_1, temp_minus, temp_quotient, temp_d, temp_u, temp_v, temp_x, result); return result; } diff --git a/Tests/LibCrypto/TestBigInteger.cpp b/Tests/LibCrypto/TestBigInteger.cpp index 615c78f3d67d6..0165bcbbd1d11 100644 --- a/Tests/LibCrypto/TestBigInteger.cpp +++ b/Tests/LibCrypto/TestBigInteger.cpp @@ -256,6 +256,40 @@ TEST_CASE(test_bigint_modular_inverse) { auto result = Crypto::NumberTheory::ModularInverse(7, 87); EXPECT_EQ(result, 25); + + // RSA-like calculations (non-prime modulus) + // 256 bits + auto result0 = Crypto::NumberTheory::ModularInverse("65537"_bigint, "7716818999704200055673002605512017774829533873852931754420182187116755406508851421710377874835807810150544004124020368281638431187393087109588616395722976"_bigint); + EXPECT_EQ(result0, "6957112022178657251467710742735822058162610570160374638904992058315050936014396238029779769209358140634220249380773356423403675888538086147825555026035553"_bigint); + + // 512 bits + auto result1 = Crypto::NumberTheory::ModularInverse("65537"_bigint, "66371585251075966819781098993500728937583856843831372038905151148345437332287092304882812087499010029105588098364783005919549558874442528396629248591406931414614111891501372333038520092291512484438801203423887203269149674846124095871663987547448839320258336408613886916453844596419759100107324930878071769740"_bigint); + EXPECT_EQ(result1, "26054622179142032720028508076442212084428946778480090764681215551421076128717366124902270573494164075542052047036494993565348604622774660543816175267575966621965870525545200512871843484053034799993241047965063186879250098185242452576259203314665246947408123972479812452501763277722372741633903726089081777013"_bigint); + + // 1024 bits + auto result2 = Crypto::NumberTheory::ModularInverse("65537"_bigint, "15138018815872997670379340569590053786751606702300795170195880218956355437896550248537760818855924336022497803648355813501714375226639621651553768492566347398869904156530722997508431839019744455406614130583767126628559642684420295498410584657359791127851130600248257172505371207271304207113156882020325681053619922800978652053485848563399633561547330701503189380714480104549363705442836720246845910476607566548831148092234175836086100548136352482086041752239158391127234701836987492763766422215181929557528346258876471603164358341122158423252911442143627060117356562382539931055979839928020375814577774568506219095460"_bigint); + EXPECT_EQ(result2, "352944027811067647898738611629058427852304118911692860827613485123904223707309287574434266615985662838432895066522539680342700540859443396609154496797860427323087928211223350781892424890095206186754144857591836206851688878370908212484113910561145014928308094010701389437847432819789627667865537264858898647327940583790765221748422671237234540519772362358619915066782513690761367501055197957446641610208834119453346877106578279102485033455183279561583102635479714079717024343606159710438913791366678187343078155600092293050263813498247677964057687773249647494687288513671987040199233950440440274115001289968681855713"_bigint); + + // 2048 bits + auto result3 = Crypto::NumberTheory::ModularInverse("65537"_bigint, "523474308603627394504956180621539730601163404544670078344572546811775850669696720017356530287979625576623354887741212994543899068001220583437221973327752079153585098984263865181019654102487287512742287583901185619943683690635892036920956164864785078974721208937251159192154678447234191958275430233568974368064153896258338157469723619961352235804796084551641896006827645045906990423304676288895690876254935487456610269572418962650650646690483258846109000171328266193988292013017586921119096421585767248613790649741313360067618201749482055683058067852760706162692126354831896695191672470846960268467251962491660154005556677209860743434696351971155125630603082354855591129257818487022326288868392996237441507506020729258165681956915422119008555908702541877086255318047295376505201886687588318922810022094799926224262663342802397393873785019139429897232975310359190270883355499980682538341383918065122655507451050546937038544941011313947405743092260202204107637846238518077467613057097554476001838993189751185435880317537273891467684330982378878693444450893688310488368914140946077563025119239896138217432169087237109636595561779480434253413579986644072788364909696328314076474006110809917696250643811113150325166438321806889977329096600"_bigint); + EXPECT_EQ(result3, "240127075385672984131139625830070783237907982221133353148189335410568341527428666156244401941613614961167400369106979053892812269120049657443477793981296225881475026790422579290126094592109424058098042199594448071964950528580600611958965243821505925343196113711042336371725072831518096843639993577853488509194999139161304606985554742922290191265996073819003163398587965470117671744141606775913928846496667921317852122223410154174992910744403897198385261335591218191096175027653809536744181084305551380061284286787205754668550681282247875856383030865885608272716379977803550823924611280514398989134855055135065370857211199581305881103457229188227055584369447256267812626743332730752890660577238791001818881550170150963398307775313919391546061252167851998883746488646960356804185182713413302894188591089552011567206439844281374992020196210238318522369271354430754186391905586095171569497490344824935263935189296620116395162680037583825943495347400986600883286030356418038099224122793594156156724989735012128839569555916857118867097884284041934024459778861054849599643478734444083949177169533378055193717492397723564200451231728283569509748271283984325804303130753631049728871294775611922359924670108389072405289815451858958044897456873"_bigint); + + // Prime modulus + // 256 bits + auto result4 = Crypto::NumberTheory::ModularInverse("79065576377430658630291493727884901955697921969202460485568061955796483998089"_bigint, "105236333148230907525852233540677623156492475210517338560791379084799836582587"_bigint); + EXPECT_EQ(result4, "93504545219772953643321957341999793447107631393924073671776287172945600034443"_bigint); + + // 512 bits + auto result5 = Crypto::NumberTheory::ModularInverse("6732413992718219635342848318074302303731222168385940253721776224551974038416513462421454674844777721589563127965274488341922551419528552939608455047714128"_bigint, "11522413189509252702442551731783393581283708206969207645140596867187940532466129960582867971721932546048110673296094625661627355203044884987258434322393611"_bigint); + EXPECT_EQ(result5, "11152730475146621030888388443393672975086889576414759677260744095766476531703359323453287638858041043666073703397243706949753685433502205695232485731849432"_bigint); + + // 1024 bits + auto result6 = Crypto::NumberTheory::ModularInverse("74263833960189886466939196560269216955870235656416128238251461763825971916420974189969964837983352188966833052749715539825280552531258436173317484112004327881741531787519471213020298642984697548930887036556763982001107471012474873100069623257613164741565312643996566523133343615723683010756027848816042939202"_bigint, "95381964444589883427387341140753255405844325814158762996484790475715776875097467150855290612578232487289384615394165716659709100194630793773552674979686871441395261056953751419334210618336786252840280983695277648363095334709545375311967459037971278965116324165577308183006400447807648095049414919774916252747"_bigint); + EXPECT_EQ(result6, "58709722343881170435829301168583511620090591717154752336044125040931850388422639576614097557227300205781894345595418512100748823628637201919915110093901598005111776632116568475789059078360021536835127742733773460624284681421890935681567846755324337116900649074136799388542272888156479298282951539364264931616"_bigint); + + // 2048 bits + auto result7 = Crypto::NumberTheory::ModularInverse("2083841562885492721290501151318058444158766003544222347122338319668970762119890042933475358898503059392439888781978346524976708635055122364241675726844930777696927712106305827918390408155067866218977660488635746552929258625544335318963328074495878439935663659069731717795216882935427203069231010795298950025561648743468756200717796561939220399337004980456668273620158478615916791124020696059432601192990947530965055857904582283829896086691653209249081553530465663724181700972927069397922147671340499270418643905380501155480764913403727582416414800901222394379992981688837765818280499497151738855424231982306618396076"_bigint, "16224364484369166277359386410182421629585266346687261081219199035627872465058014536404366328330233633748201670077151313307023144281234188494904998208639551259034363175330775169605905250528606169313713885192955997968412296964554695990505670926075345389730833276243454625387707778469967380099142375244892915645788614606443180803179195164798643205708829861402784554710221097157040790522116753790155662203858533778060827797234218324190122635514071740918420043227885163450453517325211468174509897086842869675754300089020572195273927710496253921910012981005407132203227555676309198192189264516679445448908377225879137304001"_bigint); + EXPECT_EQ(result7, "1920241917211855356722925925154440229550377096185083909958775862353126205660695403426655365321463320876264364542077391170885582314150929024605918556565268345499952616868512453484734433431514794042936426911598410457811519189984561227978039512706300456181926682048163061548216104149539350320019907684566461197120360812572564919099529762677479436223515410468281993579286727653390573176288887687204943283770190210493492026862067176323654605190038514894818679839404911730667301011930597975461644362994301634764766641419232360033891763076329125623575026815152128746383453332269905123747535275999442797020400268408062413004"_bigint); } TEST_CASE(test_bigint_even_simple_modular_power) From ec990d620f2ce0abcfb0fb5dceeb0fc859f4d6be Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 16:13:31 +0100 Subject: [PATCH 187/237] LibCrypto: Cleanup `Crypto::PK::RSA` constructors to avoid pitfalls - Removed the constructor taking a (n, d, e) tuple and moved it to `RSAPrivateKey` - Removed default constructor with key generation because it was always misused and the default key size is quite small - Added utility constructors to accept a key pair, public key, private key or both - Made constructor parameters const - Updated test to use generated random keys where possible --- Libraries/LibCrypto/PK/PK.h | 2 +- Libraries/LibCrypto/PK/RSA.h | 35 ++++++++++++-------- Libraries/LibTLS/HandshakeClient.cpp | 2 +- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 6 ++-- Tests/LibCrypto/TestRSA.cpp | 16 +++------ 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Libraries/LibCrypto/PK/PK.h b/Libraries/LibCrypto/PK/PK.h index 2d527d38de9e1..63b668185ea4b 100644 --- a/Libraries/LibCrypto/PK/PK.h +++ b/Libraries/LibCrypto/PK/PK.h @@ -126,7 +126,7 @@ class PKSystem { using PublicKeyType = PubKeyT; using PrivateKeyType = PrivKeyT; - PKSystem(PublicKeyType& pubkey, PrivateKeyType& privkey) + PKSystem(PublicKeyType const& pubkey, PrivateKeyType const& privkey) : m_public_key(pubkey) , m_private_key(privkey) { diff --git a/Libraries/LibCrypto/PK/RSA.h b/Libraries/LibCrypto/PK/RSA.h index a008eacf9df03..c1b2aa1a4a59e 100644 --- a/Libraries/LibCrypto/PK/RSA.h +++ b/Libraries/LibCrypto/PK/RSA.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -64,6 +63,14 @@ class RSAPublicKey { template class RSAPrivateKey { public: + RSAPrivateKey(Integer n, Integer d, Integer e) + : m_modulus(move(n)) + , m_private_exponent(move(d)) + , m_public_exponent(move(e)) + , m_length(m_modulus.trimmed_length() * sizeof(u32)) + { + } + RSAPrivateKey(Integer n, Integer d, Integer e, Integer p, Integer q) : m_modulus(move(n)) , m_private_exponent(move(d)) @@ -175,17 +182,27 @@ class RSA : public PKSystem, RSAPublicKey, RSAPublicKey>(pair.public_key, pair.private_key) { - m_public_key.set(n, e); - m_private_key = { n, d, e, 0, 0, 0, 0, 0 }; } - RSA(PublicKeyType& pubkey, PrivateKeyType& privkey) + RSA(PublicKeyType const& pubkey, PrivateKeyType const& privkey) : PKSystem, RSAPublicKey>(pubkey, privkey) { } + RSA(PrivateKeyType const& privkey) + { + m_private_key = privkey; + m_public_key.set(m_private_key.modulus(), m_private_key.public_exponent()); + } + + RSA(PublicKeyType const& pubkey) + { + m_public_key = pubkey; + } + RSA(ByteBuffer const& publicKeyPEM, ByteBuffer const& privateKeyPEM) { import_public_key(publicKeyPEM); @@ -198,14 +215,6 @@ class RSA : public PKSystem, RSAPublicKey out; out.resize(rsa.output_size()); diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index a4d588d69415f..63fb0271454bb 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -658,8 +658,7 @@ WebIDL::ExceptionOr> RSAOAEP::encrypt(AlgorithmParams c auto ciphertext = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(public_key.length())); auto ciphertext_bytes = ciphertext.bytes(); - auto rsa = ::Crypto::PK::RSA {}; - rsa.set_public_key(public_key); + auto rsa = ::Crypto::PK::RSA { public_key }; rsa.encrypt(padding, ciphertext_bytes); // 6. Return the result of creating an ArrayBuffer containing ciphertext. @@ -687,8 +686,7 @@ WebIDL::ExceptionOr> RSAOAEP::decrypt(AlgorithmParams c // 3. Perform the decryption operation defined in Section 7.1 of [RFC3447] with the key represented by key as the recipient's RSA private key, // the contents of ciphertext as the ciphertext to be decrypted, C, and label as the label, L, and with the hash function specified by the hash attribute // of the [[algorithm]] internal slot of key as the Hash option and MGF1 (defined in Section B.2.1 of [RFC3447]) as the MGF option. - auto rsa = ::Crypto::PK::RSA {}; - rsa.set_private_key(private_key); + auto rsa = ::Crypto::PK::RSA { private_key }; u32 private_key_length = private_key.length(); auto padding = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(private_key_length)); diff --git a/Tests/LibCrypto/TestRSA.cpp b/Tests/LibCrypto/TestRSA.cpp index 9246740897316..48ba8e3e85a4d 100644 --- a/Tests/LibCrypto/TestRSA.cpp +++ b/Tests/LibCrypto/TestRSA.cpp @@ -21,10 +21,10 @@ TEST_CASE(test_RSA_raw_encrypt) { ByteBuffer data { "hellohellohellohellohellohellohellohellohellohellohellohello123-"_b }; u8 result[] { 0x6f, 0x7b, 0xe2, 0xd3, 0x95, 0xf8, 0x8d, 0x87, 0x6d, 0x10, 0x5e, 0xc3, 0xcd, 0xf7, 0xbb, 0xa6, 0x62, 0x8e, 0x45, 0xa0, 0xf1, 0xe5, 0x0f, 0xdf, 0x69, 0xcb, 0xb6, 0xd5, 0x42, 0x06, 0x7d, 0x72, 0xa9, 0x5e, 0xae, 0xbf, 0xbf, 0x0f, 0xe0, 0xeb, 0x31, 0x31, 0xca, 0x8a, 0x81, 0x1e, 0xb9, 0xec, 0x6d, 0xcc, 0xb8, 0xa4, 0xac, 0xa3, 0x31, 0x05, 0xa9, 0xac, 0xc9, 0xd3, 0xe6, 0x2a, 0x18, 0xfe }; - Crypto::PK::RSA rsa( + Crypto::PK::RSA rsa(Crypto::PK::RSAPublicKey<> { "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, - "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, - "65537"_bigint); + "65537"_bigint, + }); u8 buffer[rsa.output_size()]; auto buf = Bytes { buffer, sizeof(buffer) }; rsa.encrypt(data, buf); @@ -35,10 +35,7 @@ TEST_CASE(test_RSA_raw_encrypt) TEST_CASE(test_RSA_PKCS_1_encrypt) { ByteBuffer data { "hellohellohellohellohellohellohellohellohello123-"_b }; - Crypto::PK::RSA_PKCS1_EME rsa( - "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, - "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, - "65537"_bigint); + Crypto::PK::RSA_PKCS1_EME rsa(Crypto::PK::RSA::generate_key_pair(1024)); u8 buffer[rsa.output_size()]; auto buf = Bytes { buffer, sizeof(buffer) }; rsa.encrypt(data, buf); @@ -153,10 +150,7 @@ c8yGzl89pYST TEST_CASE(test_RSA_encrypt_decrypt) { - Crypto::PK::RSA rsa( - "9527497237087650398000977129550904920919162360737979403539302312977329868395261515707123424679295515888026193056908173564681660256268221509339074678416049"_bigint, - "39542231845947188736992321577701849924317746648774438832456325878966594812143638244746284968851807975097653255909707366086606867657273809465195392910913"_bigint, - "65537"_bigint); + Crypto::PK::RSA rsa(Crypto::PK::RSA::generate_key_pair(1024)); u8 enc_buffer[rsa.output_size()]; u8 dec_buffer[rsa.output_size()]; From 57cc248883c69a7d7944b57a5c0ac2355658e165 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 16:16:17 +0100 Subject: [PATCH 188/237] LibCrypto: Add optimized RSA decryption with CRT method The textbook RSA decryption method of `c^d % n` is quite slow. If the necessary parameters are present, the CRT variant will be used. Performing RSA decryption this way is ~3 times faster. --- Libraries/LibCrypto/PK/RSA.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp index ebba1ebca7065..c4c5d1569bb3b 100644 --- a/Libraries/LibCrypto/PK/RSA.cpp +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -133,12 +133,24 @@ void RSA::encrypt(ReadonlyBytes in, Bytes& out) void RSA::decrypt(ReadonlyBytes in, Bytes& out) { - // FIXME: Actually use the private key properly - auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); - auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus()); - auto size = exp.export_data(out); + UnsignedBigInteger m; + if (m_private_key.prime1().is_zero() || m_private_key.prime2().is_zero()) { + m = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus()); + } else { + auto m1 = NumberTheory::ModularPower(in_integer, m_private_key.exponent1(), m_private_key.prime1()); + auto m2 = NumberTheory::ModularPower(in_integer, m_private_key.exponent2(), m_private_key.prime2()); + if (m1 < m2) + m1 = m1.plus(m_private_key.prime1()); + + VERIFY(m1 >= m2); + + auto h = NumberTheory::Mod(m1.minus(m2).multiplied_by(m_private_key.coefficient()), m_private_key.prime1()); + m = m2.plus(h.multiplied_by(m_private_key.prime2())); + } + + auto size = m.export_data(out); auto align = m_private_key.length(); auto aligned_size = (size + align - 1) / align * align; From 2174e5dfcc2928ee39f2bbbc765a10710f5fe935 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Sun, 15 Dec 2024 20:37:28 -0500 Subject: [PATCH 189/237] LibGfx: Remove ICC::Profile and friends This is not really used anymore since the fork. --- Libraries/LibGfx/CIELAB.h | 18 - Libraries/LibGfx/CMakeLists.txt | 7 - Libraries/LibGfx/DeltaE.cpp | 89 - Libraries/LibGfx/DeltaE.h | 21 - Libraries/LibGfx/ICC/BinaryFormat.h | 170 -- Libraries/LibGfx/ICC/BinaryWriter.cpp | 748 ------- Libraries/LibGfx/ICC/BinaryWriter.h | 21 - Libraries/LibGfx/ICC/DistinctFourCC.h | 78 - Libraries/LibGfx/ICC/Enums.cpp | 178 -- Libraries/LibGfx/ICC/Enums.h | 77 - Libraries/LibGfx/ICC/Profile.cpp | 1812 ----------------- Libraries/LibGfx/ICC/Profile.h | 356 ---- Libraries/LibGfx/ICC/TagTypes.cpp | 1245 ----------- Libraries/LibGfx/ICC/TagTypes.h | 1381 ------------- Libraries/LibGfx/ICC/Tags.cpp | 23 - Libraries/LibGfx/ICC/Tags.h | 89 - Libraries/LibGfx/ICC/WellKnownProfiles.cpp | 89 - Libraries/LibGfx/ICC/WellKnownProfiles.h | 21 - Meta/CMake/common_options.cmake | 1 - Meta/CMake/download_icc_profiles.cmake | 25 - Meta/Lagom/CMakeLists.txt | 1 - Meta/Lagom/Fuzzers/FuzzICCProfile.cpp | 16 - Meta/Lagom/Fuzzers/fuzzers.cmake | 2 - .../Userland/Libraries/LibGfx/BUILD.gn | 7 - Tests/LibGfx/CMakeLists.txt | 2 - Tests/LibGfx/TestDeltaE.cpp | 61 - Tests/LibGfx/TestICCProfile.cpp | 286 --- Tests/LibGfx/TestImageWriter.cpp | 20 - Utilities/icc.cpp | 605 ------ Utilities/image.cpp | 41 - 30 files changed, 7490 deletions(-) delete mode 100644 Libraries/LibGfx/CIELAB.h delete mode 100644 Libraries/LibGfx/DeltaE.cpp delete mode 100644 Libraries/LibGfx/DeltaE.h delete mode 100644 Libraries/LibGfx/ICC/BinaryFormat.h delete mode 100644 Libraries/LibGfx/ICC/BinaryWriter.cpp delete mode 100644 Libraries/LibGfx/ICC/BinaryWriter.h delete mode 100644 Libraries/LibGfx/ICC/DistinctFourCC.h delete mode 100644 Libraries/LibGfx/ICC/Enums.cpp delete mode 100644 Libraries/LibGfx/ICC/Enums.h delete mode 100644 Libraries/LibGfx/ICC/Profile.cpp delete mode 100644 Libraries/LibGfx/ICC/Profile.h delete mode 100644 Libraries/LibGfx/ICC/TagTypes.cpp delete mode 100644 Libraries/LibGfx/ICC/TagTypes.h delete mode 100644 Libraries/LibGfx/ICC/Tags.cpp delete mode 100644 Libraries/LibGfx/ICC/Tags.h delete mode 100644 Libraries/LibGfx/ICC/WellKnownProfiles.cpp delete mode 100644 Libraries/LibGfx/ICC/WellKnownProfiles.h delete mode 100644 Meta/CMake/download_icc_profiles.cmake delete mode 100644 Meta/Lagom/Fuzzers/FuzzICCProfile.cpp delete mode 100644 Tests/LibGfx/TestDeltaE.cpp delete mode 100644 Tests/LibGfx/TestICCProfile.cpp delete mode 100644 Utilities/icc.cpp diff --git a/Libraries/LibGfx/CIELAB.h b/Libraries/LibGfx/CIELAB.h deleted file mode 100644 index 6aa54f7a67618..0000000000000 --- a/Libraries/LibGfx/CIELAB.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -namespace Gfx { - -// https://en.wikipedia.org/wiki/CIELAB_color_space -struct CIELAB { - float L; // L* - float a; // a* - float b; // b* -}; - -} diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index c88d0ace897f2..d099b079bc7f2 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -8,7 +8,6 @@ set(SOURCES CMYKBitmap.cpp Color.cpp ColorSpace.cpp - DeltaE.cpp FontCascadeList.cpp Font/Font.cpp Font/FontData.cpp @@ -21,12 +20,6 @@ set(SOURCES Font/WOFF/Loader.cpp Font/WOFF2/Loader.cpp GradientPainting.cpp - ICC/BinaryWriter.cpp - ICC/Enums.cpp - ICC/Profile.cpp - ICC/Tags.cpp - ICC/TagTypes.cpp - ICC/WellKnownProfiles.cpp ImageFormats/AnimationWriter.cpp ImageFormats/BMPLoader.cpp ImageFormats/BMPWriter.cpp diff --git a/Libraries/LibGfx/DeltaE.cpp b/Libraries/LibGfx/DeltaE.cpp deleted file mode 100644 index e07137215c5af..0000000000000 --- a/Libraries/LibGfx/DeltaE.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Gfx { - -float DeltaE(CIELAB const& c1, CIELAB const& c2) -{ - // https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 - // http://zschuessler.github.io/DeltaE/learn/ - // https://www.hajim.rochester.edu/ece/sites/gsharma/ciede2000/ciede2000noteCRNA.pdf - - float delta_L_prime = c2.L - c1.L; - float L_bar = (c1.L + c2.L) / 2; - - float C1 = hypotf(c1.a, c1.b); - float C2 = hypotf(c2.a, c2.b); - float C_bar = (C1 + C2) / 2; - - float G = 0.5f * (1 - sqrtf(powf(C_bar, 7) / (powf(C_bar, 7) + powf(25, 7)))); - float a1_prime = (1 + G) * c1.a; - float a2_prime = (1 + G) * c2.a; - - float C1_prime = hypotf(a1_prime, c1.b); - float C2_prime = hypotf(a2_prime, c2.b); - - float C_prime_bar = (C1_prime + C2_prime) / 2; - float delta_C_prime = C2_prime - C1_prime; - - auto h_prime = [](float b, float a_prime) { - if (b == 0 && a_prime == 0) - return 0.f; - float h_prime = atan2(b, a_prime); - if (h_prime < 0) - h_prime += 2 * static_cast(M_PI); - return AK::to_degrees(h_prime); - }; - float h1_prime = h_prime(c1.b, a1_prime); - float h2_prime = h_prime(c2.b, a2_prime); - - float delta_h_prime; - if (C1_prime == 0 || C2_prime == 0) - delta_h_prime = 0; - else if (fabsf(h1_prime - h2_prime) <= 180.f) - delta_h_prime = h2_prime - h1_prime; - else if (h2_prime <= h1_prime) - delta_h_prime = h2_prime - h1_prime + 360; - else - delta_h_prime = h2_prime - h1_prime - 360; - - auto sin_degrees = [](float x) { return sinf(AK::to_radians(x)); }; - auto cos_degrees = [](float x) { return cosf(AK::to_radians(x)); }; - - float delta_H_prime = 2 * sqrtf(C1_prime * C2_prime) * sin_degrees(delta_h_prime / 2); - - float h_prime_bar; - if (C1_prime == 0 || C2_prime == 0) - h_prime_bar = h1_prime + h2_prime; - else if (fabsf(h1_prime - h2_prime) <= 180.f) - h_prime_bar = (h1_prime + h2_prime) / 2; - else if (h1_prime + h2_prime < 360) - h_prime_bar = (h1_prime + h2_prime + 360) / 2; - else - h_prime_bar = (h1_prime + h2_prime - 360) / 2; - - float T = 1 - 0.17f * cos_degrees(h_prime_bar - 30) + 0.24f * cos_degrees(2 * h_prime_bar) + 0.32f * cos_degrees(3 * h_prime_bar + 6) - 0.2f * cos_degrees(4 * h_prime_bar - 63); - - float S_L = 1 + 0.015f * powf(L_bar - 50, 2) / sqrtf(20 + powf(L_bar - 50, 2)); - float S_C = 1 + 0.045f * C_prime_bar; - float S_H = 1 + 0.015f * C_prime_bar * T; - - float R_T = -2 * sqrtf(powf(C_prime_bar, 7) / (powf(C_prime_bar, 7) + powf(25, 7))) * sin_degrees(60 * exp(-powf((h_prime_bar - 275) / 25, 2))); - - // "kL, kC, and kH are usually unity." - float k_L = 1, k_C = 1, k_H = 1; - - float L = delta_L_prime / (k_L * S_L); - float C = delta_C_prime / (k_C * S_C); - float H = delta_H_prime / (k_H * S_H); - return sqrtf(powf(L, 2) + powf(C, 2) + powf(H, 2) + R_T * C * H); -} - -} diff --git a/Libraries/LibGfx/DeltaE.h b/Libraries/LibGfx/DeltaE.h deleted file mode 100644 index c36e9cdc49ebd..0000000000000 --- a/Libraries/LibGfx/DeltaE.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Gfx { - -// Returns a number between 0 and 100 that describes how far apart two colors are in human perception. -// A return value < 1 means that the two colors are not noticeably different. -// The larger the return value, the easier it is to tell the two colors apart. -// Works better for colors that are somewhat "close". -// -// You can use ICC::sRGB()->to_lab() to convert sRGB colors to CIELAB. -float DeltaE(CIELAB const&, CIELAB const&); - -} diff --git a/Libraries/LibGfx/ICC/BinaryFormat.h b/Libraries/LibGfx/ICC/BinaryFormat.h deleted file mode 100644 index c15486f06fbbb..0000000000000 --- a/Libraries/LibGfx/ICC/BinaryFormat.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Gfx::ICC { - -// ICC V4, 4.2 dateTimeNumber -// "All the dateTimeNumber values in a profile shall be in Coordinated Universal Time [...]." -struct DateTimeNumber { - BigEndian year; - BigEndian month; - BigEndian day; - BigEndian hours; - BigEndian minutes; - BigEndian seconds; -}; - -// ICC V4, 4.6 s15Fixed16Number -using s15Fixed16Number = i32; - -// ICC V4, 4.7 u16Fixed16Number -using u16Fixed16Number = u32; - -// ICC V4, 4.14 XYZNumber -struct XYZNumber { - BigEndian X; - BigEndian Y; - BigEndian Z; - - XYZNumber() = default; - - XYZNumber(XYZ const& xyz) - : X(round(xyz.X * 0x1'0000)) - , Y(round(xyz.Y * 0x1'0000)) - , Z(round(xyz.Z * 0x1'0000)) - { - } - - operator XYZ() const - { - return XYZ { X / (float)0x1'0000, Y / (float)0x1'0000, Z / (float)0x1'0000 }; - } -}; - -// ICC V4, 7.2 Profile header -struct ICCHeader { - BigEndian profile_size; - BigEndian preferred_cmm_type; - - u8 profile_version_major; - u8 profile_version_minor_bugfix; - BigEndian profile_version_zero; - - BigEndian profile_device_class; - BigEndian data_color_space; - BigEndian profile_connection_space; // "PCS" in the spec. - - DateTimeNumber profile_creation_time; - - BigEndian profile_file_signature; - BigEndian primary_platform; - - BigEndian profile_flags; - BigEndian device_manufacturer; - BigEndian device_model; - BigEndian device_attributes; - BigEndian rendering_intent; - - XYZNumber pcs_illuminant; - - BigEndian profile_creator; - - u8 profile_id[16]; - u8 reserved[28]; -}; -static_assert(AssertSize()); - -// ICC v4, 7.2.9 Profile file signature field -// "The profile file signature field shall contain the value “acsp” (61637370h) as a profile file signature." -constexpr u32 ProfileFileSignature = 0x61637370; - -// ICC V4, 7.3 Tag table, Table 24 - Tag table structure -struct TagTableEntry { - BigEndian tag_signature; - BigEndian offset_to_beginning_of_tag_data_element; - BigEndian size_of_tag_data_element; -}; -static_assert(AssertSize()); - -// Common bits of ICC v4, Table 40 — lut16Type encoding and Table 44 — lut8Type encoding -struct LUTHeader { - u8 number_of_input_channels; - u8 number_of_output_channels; - u8 number_of_clut_grid_points; - u8 reserved_for_padding; - BigEndian e_parameters[9]; -}; -static_assert(AssertSize()); - -// Common bits of ICC v4, Table 45 — lutAToBType encoding and Table 47 — lutBToAType encoding -struct AdvancedLUTHeader { - u8 number_of_input_channels; - u8 number_of_output_channels; - BigEndian reserved_for_padding; - BigEndian offset_to_b_curves; - BigEndian offset_to_matrix; - BigEndian offset_to_m_curves; - BigEndian offset_to_clut; - BigEndian offset_to_a_curves; -}; -static_assert(AssertSize()); - -// ICC v4, Table 46 — lutAToBType CLUT encoding -// ICC v4, Table 48 — lutBToAType CLUT encoding -// (They're identical.) -struct CLUTHeader { - u8 number_of_grid_points_in_dimension[16]; - u8 precision_of_data_elements; // 1 for u8 entries, 2 for u16 entries. - u8 reserved_for_padding[3]; -}; -static_assert(AssertSize()); - -// Table 49 — measurementType structure -struct MeasurementHeader { - BigEndian standard_observer; - XYZNumber tristimulus_value_for_measurement_backing; - BigEndian measurement_geometry; - BigEndian measurement_flare; - BigEndian standard_illuminant; -}; -static_assert(AssertSize()); - -// ICC v4, 10.15 multiLocalizedUnicodeType -struct MultiLocalizedUnicodeRawRecord { - BigEndian language_code; - BigEndian country_code; - BigEndian string_length_in_bytes; - BigEndian string_offset_in_bytes; -}; -static_assert(AssertSize()); - -// Table 66 — namedColor2Type encoding -struct NamedColorHeader { - BigEndian vendor_specific_flag; - BigEndian count_of_named_colors; - BigEndian number_of_device_coordinates_of_each_named_color; - u8 prefix_for_each_color_name[32]; // null-terminated - u8 suffix_for_each_color_name[32]; // null-terminated -}; -static_assert(AssertSize()); - -// Table 84 — viewingConditionsType encoding -struct ViewingConditionsHeader { - XYZNumber unnormalized_ciexyz_values_for_illuminant; // "(in which Y is in cd/m2)" - XYZNumber unnormalized_ciexyz_values_for_surround; // "(in which Y is in cd/m2)" - BigEndian illuminant_type; -}; -static_assert(AssertSize()); - -} diff --git a/Libraries/LibGfx/ICC/BinaryWriter.cpp b/Libraries/LibGfx/ICC/BinaryWriter.cpp deleted file mode 100644 index 3912c3bc674ac..0000000000000 --- a/Libraries/LibGfx/ICC/BinaryWriter.cpp +++ /dev/null @@ -1,748 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -#pragma GCC diagnostic ignored "-Warray-bounds" - -namespace Gfx::ICC { - -static ErrorOr encode_chromaticity(ChromaticityTagData const& tag_data) -{ - // ICC v4, 10.2 chromaticityType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + 2 * sizeof(u16) + tag_data.xy_coordinates().size() * 2 * sizeof(u16Fixed16Number))); - - *bit_cast*>(bytes.data()) = static_cast(ChromaticityTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - *bit_cast*>(bytes.data() + 8) = tag_data.xy_coordinates().size(); - *bit_cast*>(bytes.data() + 10) = static_cast(tag_data.phosphor_or_colorant_type()); - - auto* coordinates = bit_cast*>(bytes.data() + 12); - for (size_t i = 0; i < tag_data.xy_coordinates().size(); ++i) { - coordinates[2 * i] = tag_data.xy_coordinates()[i].x.raw(); - coordinates[2 * i + 1] = tag_data.xy_coordinates()[i].y.raw(); - } - - return bytes; -} - -static ErrorOr encode_cipc(CicpTagData const& tag_data) -{ - // ICC v4, 10.3 cicpType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + 4)); - *bit_cast*>(bytes.data()) = static_cast(CicpTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - bytes.data()[8] = tag_data.color_primaries(); - bytes.data()[9] = tag_data.transfer_characteristics(); - bytes.data()[10] = tag_data.matrix_coefficients(); - bytes.data()[11] = tag_data.video_full_range_flag(); - return bytes; -} - -static u32 curve_encoded_size(CurveTagData const& tag_data) -{ - return 3 * sizeof(u32) + tag_data.values().size() * sizeof(u16); -} - -static void encode_curve_to(CurveTagData const& tag_data, Bytes bytes) -{ - *bit_cast*>(bytes.data()) = static_cast(CurveTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - *bit_cast*>(bytes.data() + 8) = tag_data.values().size(); - - auto* values = bit_cast*>(bytes.data() + 12); - for (size_t i = 0; i < tag_data.values().size(); ++i) - values[i] = tag_data.values()[i]; -} - -static ErrorOr encode_curve(CurveTagData const& tag_data) -{ - // ICC v4, 10.6 curveType - auto bytes = TRY(ByteBuffer::create_uninitialized(curve_encoded_size(tag_data))); - encode_curve_to(tag_data, bytes.bytes()); - return bytes; -} - -static ErrorOr encode_lut_16(Lut16TagData const& tag_data) -{ - // ICC v4, 10.10 lut16Type - u32 input_tables_size = tag_data.input_tables().size(); - u32 clut_values_size = tag_data.clut_values().size(); - u32 output_tables_size = tag_data.output_tables().size(); - - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(LUTHeader) + 2 * sizeof(u16) + sizeof(u16) * (input_tables_size + clut_values_size + output_tables_size))); - *bit_cast*>(bytes.data()) = static_cast(Lut16TagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.number_of_input_channels = tag_data.number_of_input_channels(); - header.number_of_output_channels = tag_data.number_of_output_channels(); - header.number_of_clut_grid_points = tag_data.number_of_clut_grid_points(); - header.reserved_for_padding = 0; - for (int i = 0; i < 9; ++i) - header.e_parameters[i] = tag_data.e_matrix().e[i].raw(); - - *bit_cast*>(bytes.data() + 8 + sizeof(LUTHeader)) = tag_data.number_of_input_table_entries(); - *bit_cast*>(bytes.data() + 8 + sizeof(LUTHeader) + 2) = tag_data.number_of_output_table_entries(); - - auto* values = bit_cast*>(bytes.data() + 8 + sizeof(LUTHeader) + 4); - for (u16 input_value : tag_data.input_tables()) - *values++ = input_value; - - for (u16 clut_value : tag_data.clut_values()) - *values++ = clut_value; - - for (u16 output_value : tag_data.output_tables()) - *values++ = output_value; - - return bytes; -} - -static ErrorOr encode_lut_8(Lut8TagData const& tag_data) -{ - // ICC v4, 10.11 lut8Type - u32 input_tables_size = tag_data.input_tables().size(); - u32 clut_values_size = tag_data.clut_values().size(); - u32 output_tables_size = tag_data.output_tables().size(); - - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(LUTHeader) + input_tables_size + clut_values_size + output_tables_size)); - *bit_cast*>(bytes.data()) = static_cast(Lut8TagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.number_of_input_channels = tag_data.number_of_input_channels(); - header.number_of_output_channels = tag_data.number_of_output_channels(); - header.number_of_clut_grid_points = tag_data.number_of_clut_grid_points(); - header.reserved_for_padding = 0; - for (int i = 0; i < 9; ++i) - header.e_parameters[i] = tag_data.e_matrix().e[i].raw(); - - u8* values = bytes.data() + 8 + sizeof(LUTHeader); - memcpy(values, tag_data.input_tables().data(), input_tables_size); - values += input_tables_size; - - memcpy(values, tag_data.clut_values().data(), clut_values_size); - values += clut_values_size; - - memcpy(values, tag_data.output_tables().data(), output_tables_size); - - return bytes; -} - -static u32 curve_encoded_size(CurveTagData const&); -static void encode_curve_to(CurveTagData const&, Bytes); -static u32 parametric_curve_encoded_size(ParametricCurveTagData const&); -static void encode_parametric_curve_to(ParametricCurveTagData const&, Bytes); - -static u32 byte_size_of_curve(LutCurveType const& curve) -{ - VERIFY(curve->type() == Gfx::ICC::CurveTagData::Type || curve->type() == Gfx::ICC::ParametricCurveTagData::Type); - if (curve->type() == Gfx::ICC::CurveTagData::Type) - return curve_encoded_size(static_cast(*curve)); - return parametric_curve_encoded_size(static_cast(*curve)); -} - -static u32 byte_size_of_curves(Vector const& curves) -{ - u32 size = 0; - for (auto const& curve : curves) - size += align_up_to(byte_size_of_curve(curve), 4); - return size; -} - -static void write_curve(Bytes bytes, LutCurveType const& curve) -{ - VERIFY(curve->type() == Gfx::ICC::CurveTagData::Type || curve->type() == Gfx::ICC::ParametricCurveTagData::Type); - if (curve->type() == Gfx::ICC::CurveTagData::Type) - encode_curve_to(static_cast(*curve), bytes); - if (curve->type() == Gfx::ICC::ParametricCurveTagData::Type) - encode_parametric_curve_to(static_cast(*curve), bytes); -} - -static void write_curves(Bytes bytes, Vector const& curves) -{ - u32 offset = 0; - for (auto const& curve : curves) { - u32 size = byte_size_of_curve(curve); - write_curve(bytes.slice(offset, size), curve); - offset += align_up_to(size, 4); - } -} - -static u32 byte_size_of_clut(CLUTData const& clut) -{ - u32 data_size = clut.values.visit( - [](Vector const& v) { return v.size(); }, - [](Vector const& v) { return 2 * v.size(); }); - return align_up_to(sizeof(CLUTHeader) + data_size, 4); -} - -static void write_clut(Bytes bytes, CLUTData const& clut) -{ - auto& clut_header = *bit_cast(bytes.data()); - memset(clut_header.number_of_grid_points_in_dimension, 0, sizeof(clut_header.number_of_grid_points_in_dimension)); - VERIFY(clut.number_of_grid_points_in_dimension.size() <= sizeof(clut_header.number_of_grid_points_in_dimension)); - for (size_t i = 0; i < clut.number_of_grid_points_in_dimension.size(); ++i) - clut_header.number_of_grid_points_in_dimension[i] = clut.number_of_grid_points_in_dimension[i]; - - clut_header.precision_of_data_elements = clut.values.visit( - [](Vector const&) { return 1; }, - [](Vector const&) { return 2; }); - - memset(clut_header.reserved_for_padding, 0, sizeof(clut_header.reserved_for_padding)); - - clut.values.visit( - [&bytes](Vector const& v) { - memcpy(bytes.data() + sizeof(CLUTHeader), v.data(), v.size()); - }, - [&bytes](Vector const& v) { - auto* raw_clut = bit_cast*>(bytes.data() + sizeof(CLUTHeader)); - for (size_t i = 0; i < v.size(); ++i) - raw_clut[i] = v[i]; - }); -} - -static void write_matrix(Bytes bytes, EMatrix3x4 const& e_matrix) -{ - auto* raw_e = bit_cast*>(bytes.data()); - for (int i = 0; i < 12; ++i) - raw_e[i] = e_matrix.e[i].raw(); -} - -static ErrorOr encode_lut_a_to_b(LutAToBTagData const& tag_data) -{ - // ICC v4, 10.12 lutAToBType - u32 a_curves_size = tag_data.a_curves().map(byte_size_of_curves).value_or(0); - u32 clut_size = tag_data.clut().map(byte_size_of_clut).value_or(0); - u32 m_curves_size = tag_data.m_curves().map(byte_size_of_curves).value_or(0); - u32 e_matrix_size = tag_data.e_matrix().has_value() ? 12 * sizeof(s15Fixed16Number) : 0; - u32 b_curves_size = byte_size_of_curves(tag_data.b_curves()); - - auto bytes = TRY(ByteBuffer::create_zeroed(2 * sizeof(u32) + sizeof(AdvancedLUTHeader) + a_curves_size + clut_size + m_curves_size + e_matrix_size + b_curves_size)); - *bit_cast*>(bytes.data()) = static_cast(LutAToBTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.number_of_input_channels = tag_data.number_of_input_channels(); - header.number_of_output_channels = tag_data.number_of_output_channels(); - header.reserved_for_padding = 0; - header.offset_to_b_curves = 0; - header.offset_to_matrix = 0; - header.offset_to_m_curves = 0; - header.offset_to_clut = 0; - header.offset_to_a_curves = 0; - - u32 offset = 2 * sizeof(u32) + sizeof(AdvancedLUTHeader); - auto advance = [&offset](BigEndian& header_slot, u32 size) { - header_slot = offset; - VERIFY(size % 4 == 0); - offset += size; - }; - - if (auto const& a_curves = tag_data.a_curves(); a_curves.has_value()) { - write_curves(bytes.bytes().slice(offset, a_curves_size), a_curves.value()); - advance(header.offset_to_a_curves, a_curves_size); - } - if (auto const& clut = tag_data.clut(); clut.has_value()) { - write_clut(bytes.bytes().slice(offset, clut_size), clut.value()); - advance(header.offset_to_clut, clut_size); - } - if (auto const& m_curves = tag_data.m_curves(); m_curves.has_value()) { - write_curves(bytes.bytes().slice(offset, m_curves_size), m_curves.value()); - advance(header.offset_to_m_curves, m_curves_size); - } - if (auto const& e_matrix = tag_data.e_matrix(); e_matrix.has_value()) { - write_matrix(bytes.bytes().slice(offset, e_matrix_size), e_matrix.value()); - advance(header.offset_to_matrix, e_matrix_size); - } - write_curves(bytes.bytes().slice(offset, b_curves_size), tag_data.b_curves()); - advance(header.offset_to_b_curves, b_curves_size); - - return bytes; -} - -static ErrorOr encode_lut_b_to_a(LutBToATagData const& tag_data) -{ - // ICC v4, 10.13 lutBToAType - u32 b_curves_size = byte_size_of_curves(tag_data.b_curves()); - u32 e_matrix_size = tag_data.e_matrix().has_value() ? 12 * sizeof(s15Fixed16Number) : 0; - u32 m_curves_size = tag_data.m_curves().map(byte_size_of_curves).value_or(0); - u32 clut_size = tag_data.clut().map(byte_size_of_clut).value_or(0); - u32 a_curves_size = tag_data.a_curves().map(byte_size_of_curves).value_or(0); - - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(AdvancedLUTHeader) + b_curves_size + e_matrix_size + m_curves_size + clut_size + a_curves_size)); - *bit_cast*>(bytes.data()) = static_cast(LutBToATagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.number_of_input_channels = tag_data.number_of_input_channels(); - header.number_of_output_channels = tag_data.number_of_output_channels(); - header.reserved_for_padding = 0; - header.offset_to_b_curves = 0; - header.offset_to_matrix = 0; - header.offset_to_m_curves = 0; - header.offset_to_clut = 0; - header.offset_to_a_curves = 0; - - u32 offset = 2 * sizeof(u32) + sizeof(AdvancedLUTHeader); - auto advance = [&offset](BigEndian& header_slot, u32 size) { - header_slot = offset; - VERIFY(size % 4 == 0); - offset += size; - }; - - write_curves(bytes.bytes().slice(offset, b_curves_size), tag_data.b_curves()); - advance(header.offset_to_b_curves, b_curves_size); - if (auto const& e_matrix = tag_data.e_matrix(); e_matrix.has_value()) { - write_matrix(bytes.bytes().slice(offset, e_matrix_size), e_matrix.value()); - advance(header.offset_to_matrix, e_matrix_size); - } - if (auto const& m_curves = tag_data.m_curves(); m_curves.has_value()) { - write_curves(bytes.bytes().slice(offset, m_curves_size), m_curves.value()); - advance(header.offset_to_m_curves, m_curves_size); - } - if (auto const& clut = tag_data.clut(); clut.has_value()) { - write_clut(bytes.bytes().slice(offset, clut_size), clut.value()); - advance(header.offset_to_clut, clut_size); - } - if (auto const& a_curves = tag_data.a_curves(); a_curves.has_value()) { - write_curves(bytes.bytes().slice(offset, a_curves_size), a_curves.value()); - advance(header.offset_to_a_curves, a_curves_size); - } - - return bytes; -} - -static ErrorOr encode_measurement(MeasurementTagData const& tag_data) -{ - // ICC v4, 10.14 measurementType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(MeasurementHeader))); - *bit_cast*>(bytes.data()) = static_cast(MeasurementTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.standard_observer = tag_data.standard_observer(); - header.tristimulus_value_for_measurement_backing = tag_data.tristimulus_value_for_measurement_backing(); - header.measurement_geometry = tag_data.measurement_geometry(); - header.measurement_flare = tag_data.measurement_flare().raw(); - header.standard_illuminant = tag_data.standard_illuminant(); - - return bytes; -} - -static ErrorOr encode_multi_localized_unicode(MultiLocalizedUnicodeTagData const& tag_data) -{ - // ICC v4, 10.15 multiLocalizedUnicodeType - // "The Unicode strings in storage should be encoded as 16-bit big-endian, UTF-16BE, - // and should not be NULL terminated." - size_t number_of_records = tag_data.records().size(); - size_t header_and_record_size = 4 * sizeof(u32) + number_of_records * sizeof(MultiLocalizedUnicodeRawRecord); - - size_t number_of_codepoints = 0; - Vector utf16_strings; - TRY(utf16_strings.try_ensure_capacity(number_of_records)); - for (auto const& record : tag_data.records()) { - TRY(utf16_strings.try_append(TRY(utf8_to_utf16(record.text)))); - number_of_codepoints += utf16_strings.last().size(); - } - - size_t string_table_size = number_of_codepoints * sizeof(u16); - - auto bytes = TRY(ByteBuffer::create_uninitialized(header_and_record_size + string_table_size)); - - auto* header = bit_cast*>(bytes.data()); - header[0] = static_cast(MultiLocalizedUnicodeTagData::Type); - header[1] = 0; - header[2] = number_of_records; - header[3] = sizeof(MultiLocalizedUnicodeRawRecord); - - size_t offset = header_and_record_size; - auto* records = bit_cast(bytes.data() + 16); - for (size_t i = 0; i < number_of_records; ++i) { - records[i].language_code = tag_data.records()[i].iso_639_1_language_code; - records[i].country_code = tag_data.records()[i].iso_3166_1_country_code; - records[i].string_length_in_bytes = utf16_strings[i].size() * sizeof(u16); - records[i].string_offset_in_bytes = offset; - offset += records[i].string_length_in_bytes; - } - - auto* string_table = bit_cast*>(bytes.data() + header_and_record_size); - for (auto const& utf16_string : utf16_strings) { - for (size_t i = 0; i < utf16_string.size(); ++i) - string_table[i] = utf16_string[i]; - string_table += utf16_string.size(); - } - - return bytes; -} - -static ErrorOr encode_named_color_2(NamedColor2TagData const& tag_data) -{ - // ICC v4, 10.17 namedColor2Type - unsigned const record_byte_size = 32 + sizeof(u16) * (3 + tag_data.number_of_device_coordinates()); - - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(NamedColorHeader) + tag_data.size() * record_byte_size)); - *bit_cast*>(bytes.data()) = (u32)NamedColor2TagData::Type; - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.vendor_specific_flag = tag_data.vendor_specific_flag(); - header.count_of_named_colors = tag_data.size(); - header.number_of_device_coordinates_of_each_named_color = tag_data.number_of_device_coordinates(); - memset(header.prefix_for_each_color_name, 0, 32); - memcpy(header.prefix_for_each_color_name, tag_data.prefix().bytes().data(), tag_data.prefix().bytes().size()); - memset(header.suffix_for_each_color_name, 0, 32); - memcpy(header.suffix_for_each_color_name, tag_data.suffix().bytes().data(), tag_data.suffix().bytes().size()); - - u8* record = bytes.data() + 8 + sizeof(NamedColorHeader); - for (size_t i = 0; i < tag_data.size(); ++i) { - memset(record, 0, 32); - memcpy(record, tag_data.root_name(i).bytes().data(), tag_data.root_name(i).bytes().size()); - - auto* components = bit_cast*>(record + 32); - components[0] = tag_data.pcs_coordinates(i).xyz.x; - components[1] = tag_data.pcs_coordinates(i).xyz.y; - components[2] = tag_data.pcs_coordinates(i).xyz.z; - for (size_t j = 0; j < tag_data.number_of_device_coordinates(); ++j) - components[3 + j] = tag_data.device_coordinates(i)[j]; - - record += record_byte_size; - } - - return bytes; -} - -static u32 parametric_curve_encoded_size(ParametricCurveTagData const& tag_data) -{ - return 2 * sizeof(u32) + 2 * sizeof(u16) + tag_data.parameter_count() * sizeof(s15Fixed16Number); -} - -static void encode_parametric_curve_to(ParametricCurveTagData const& tag_data, Bytes bytes) -{ - *bit_cast*>(bytes.data()) = static_cast(ParametricCurveTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - *bit_cast*>(bytes.data() + 8) = static_cast(tag_data.function_type()); - *bit_cast*>(bytes.data() + 10) = 0; - - auto* parameters = bit_cast*>(bytes.data() + 12); - for (size_t i = 0; i < tag_data.parameter_count(); ++i) - parameters[i] = tag_data.parameter(i).raw(); -} - -static ErrorOr encode_parametric_curve(ParametricCurveTagData const& tag_data) -{ - // ICC v4, 10.18 parametricCurveType - auto bytes = TRY(ByteBuffer::create_uninitialized(parametric_curve_encoded_size(tag_data))); - encode_parametric_curve_to(tag_data, bytes.bytes()); - return bytes; -} - -static ErrorOr encode_s15_fixed_array(S15Fixed16ArrayTagData const& tag_data) -{ - // ICC v4, 10.22 s15Fixed16ArrayType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + tag_data.values().size() * sizeof(s15Fixed16Number))); - *bit_cast*>(bytes.data()) = static_cast(S15Fixed16ArrayTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto* values = bit_cast*>(bytes.data() + 8); - for (size_t i = 0; i < tag_data.values().size(); ++i) - values[i] = tag_data.values()[i].raw(); - - return bytes; -} - -static ErrorOr encode_signature(SignatureTagData const& tag_data) -{ - // ICC v4, 10.23 signatureType - auto bytes = TRY(ByteBuffer::create_uninitialized(3 * sizeof(u32))); - *bit_cast*>(bytes.data()) = static_cast(SignatureTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - *bit_cast*>(bytes.data() + 8) = tag_data.signature(); - return bytes; -} - -static ErrorOr encode_text_description(TextDescriptionTagData const& tag_data) -{ - // ICC v2, 6.5.17 textDescriptionType - // All lengths include room for a trailing nul character. - // See also the many comments in TextDescriptionTagData::from_bytes(). - u32 ascii_size = sizeof(u32) + tag_data.ascii_description().bytes().size() + 1; - - // FIXME: Include tag_data.unicode_description() if it's set. - u32 unicode_size = 2 * sizeof(u32); - - // FIXME: Include tag_data.macintosh_description() if it's set. - u32 macintosh_size = sizeof(u16) + sizeof(u8) + 67; - - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + ascii_size + unicode_size + macintosh_size)); - *bit_cast*>(bytes.data()) = static_cast(TextDescriptionTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - // ASCII - *bit_cast*>(bytes.data() + 8) = tag_data.ascii_description().bytes().size() + 1; - memcpy(bytes.data() + 12, tag_data.ascii_description().bytes().data(), tag_data.ascii_description().bytes().size()); - bytes.data()[12 + tag_data.ascii_description().bytes().size()] = '\0'; - - // Unicode - // "Because the Unicode language code and Unicode count immediately follow the ASCII description, - // their alignment is not correct when the ASCII count is not a multiple of four" - // So we can't use BigEndian here. - u8* cursor = bytes.data() + 8 + ascii_size; - u32 unicode_language_code = 0; // FIXME: Set to tag_data.unicode_language_code() once this writes unicode data. - cursor[0] = unicode_language_code >> 24; - cursor[1] = (unicode_language_code >> 16) & 0xff; - cursor[2] = (unicode_language_code >> 8) & 0xff; - cursor[3] = unicode_language_code & 0xff; - cursor += 4; - - // FIXME: Include tag_data.unicode_description() if it's set. - u32 ucs2_count = 0; // FIXME: If tag_data.unicode_description() is set, set this to its length plus room for one nul character. - cursor[0] = ucs2_count >> 24; - cursor[1] = (ucs2_count >> 16) & 0xff; - cursor[2] = (ucs2_count >> 8) & 0xff; - cursor[3] = ucs2_count & 0xff; - cursor += 4; - - // Macintosh scriptcode - u16 scriptcode_code = 0; // MacRoman - cursor[0] = (scriptcode_code >> 8) & 0xff; - cursor[1] = scriptcode_code & 0xff; - cursor += 2; - - u8 macintosh_description_length = 0; // FIXME: If tag_data.macintosh_description() is set, set this to tis length plus room for one nul character. - cursor[0] = macintosh_description_length; - cursor += 1; - memset(cursor, 0, 67); - - return bytes; -} - -static ErrorOr encode_text(TextTagData const& tag_data) -{ - // ICC v4, 10.24 textType - // "The textType is a simple text structure that contains a 7-bit ASCII text string. The length of the string is obtained - // by subtracting 8 from the element size portion of the tag itself. This string shall be terminated with a 00h byte." - auto text_bytes = tag_data.text().bytes(); - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + text_bytes.size() + 1)); - *bit_cast*>(bytes.data()) = static_cast(TextTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - memcpy(bytes.data() + 8, text_bytes.data(), text_bytes.size()); - *(bytes.data() + 8 + text_bytes.size()) = '\0'; - return bytes; -} - -static ErrorOr encode_viewing_conditions(ViewingConditionsTagData const& tag_data) -{ - // ICC v4, 10.30 viewingConditionsType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + sizeof(ViewingConditionsHeader))); - *bit_cast*>(bytes.data()) = static_cast(ViewingConditionsTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto& header = *bit_cast(bytes.data() + 8); - header.unnormalized_ciexyz_values_for_illuminant = tag_data.unnormalized_ciexyz_values_for_illuminant(); - header.unnormalized_ciexyz_values_for_surround = tag_data.unnormalized_ciexyz_values_for_surround(); - header.illuminant_type = tag_data.illuminant_type(); - - return bytes; -} - -static ErrorOr encode_xyz(XYZTagData const& tag_data) -{ - // ICC v4, 10.31 XYZType - auto bytes = TRY(ByteBuffer::create_uninitialized(2 * sizeof(u32) + tag_data.xyzs().size() * sizeof(XYZNumber))); - *bit_cast*>(bytes.data()) = static_cast(XYZTagData::Type); - *bit_cast*>(bytes.data() + 4) = 0; - - auto* xyzs = bit_cast(bytes.data() + 8); - for (size_t i = 0; i < tag_data.xyzs().size(); ++i) - xyzs[i] = tag_data.xyzs()[i]; - - return bytes; -} - -static ErrorOr> encode_tag_data(TagData const& tag_data) -{ - switch (tag_data.type()) { - case ChromaticityTagData::Type: - return encode_chromaticity(static_cast(tag_data)); - case CicpTagData::Type: - return encode_cipc(static_cast(tag_data)); - case CurveTagData::Type: - return encode_curve(static_cast(tag_data)); - case Lut16TagData::Type: - return encode_lut_16(static_cast(tag_data)); - case Lut8TagData::Type: - return encode_lut_8(static_cast(tag_data)); - case LutAToBTagData::Type: - return encode_lut_a_to_b(static_cast(tag_data)); - case LutBToATagData::Type: - return encode_lut_b_to_a(static_cast(tag_data)); - case MeasurementTagData::Type: - return encode_measurement(static_cast(tag_data)); - case MultiLocalizedUnicodeTagData::Type: - return encode_multi_localized_unicode(static_cast(tag_data)); - case NamedColor2TagData::Type: - return encode_named_color_2(static_cast(tag_data)); - case ParametricCurveTagData::Type: - return encode_parametric_curve(static_cast(tag_data)); - case S15Fixed16ArrayTagData::Type: - return encode_s15_fixed_array(static_cast(tag_data)); - case SignatureTagData::Type: - return encode_signature(static_cast(tag_data)); - case TextDescriptionTagData::Type: - return encode_text_description(static_cast(tag_data)); - case TextTagData::Type: - return encode_text(static_cast(tag_data)); - case ViewingConditionsTagData::Type: - return encode_viewing_conditions(static_cast(tag_data)); - case XYZTagData::Type: - return encode_xyz(static_cast(tag_data)); - } - - return OptionalNone {}; -} - -static ErrorOr> encode_tag_datas(Profile const& profile, HashMap& tag_data_map) -{ - Vector tag_data_bytes; - TRY(tag_data_bytes.try_ensure_capacity(profile.tag_count())); - - TRY(profile.try_for_each_tag([&](auto, auto tag_data) -> ErrorOr { - if (tag_data_map.contains(tag_data.ptr())) - return {}; - - auto encoded_tag_data = TRY(encode_tag_data(tag_data)); - if (!encoded_tag_data.has_value()) - return {}; - - tag_data_bytes.append(encoded_tag_data.release_value()); - TRY(tag_data_map.try_set(tag_data.ptr(), tag_data_bytes.size() - 1)); - return {}; - })); - return tag_data_bytes; -} - -static ErrorOr encode_tag_table(ByteBuffer& bytes, Profile const& profile, u32 number_of_serialized_tags, Vector const& offsets, - Vector const& tag_data_bytes, HashMap const& tag_data_map) -{ - // ICC v4, 7.3 Tag table - // ICC v4, 7.3.1 Overview - VERIFY(bytes.size() >= sizeof(ICCHeader) + sizeof(u32) + number_of_serialized_tags * sizeof(TagTableEntry)); - - *bit_cast*>(bytes.data() + sizeof(ICCHeader)) = number_of_serialized_tags; - - TagTableEntry* tag_table_entries = bit_cast(bytes.data() + sizeof(ICCHeader) + sizeof(u32)); - int i = 0; - profile.for_each_tag([&](auto tag_signature, auto tag_data) { - auto index = tag_data_map.get(tag_data.ptr()); - if (!index.has_value()) - return; - - tag_table_entries[i].tag_signature = tag_signature; - - tag_table_entries[i].offset_to_beginning_of_tag_data_element = offsets[index.value()]; - tag_table_entries[i].size_of_tag_data_element = tag_data_bytes[index.value()].size(); - ++i; - }); - - return {}; -} - -static ErrorOr encode_header(ByteBuffer& bytes, Profile const& profile) -{ - VERIFY(bytes.size() >= sizeof(ICCHeader)); - auto& raw_header = *bit_cast(bytes.data()); - - raw_header.profile_size = bytes.size(); - raw_header.preferred_cmm_type = profile.preferred_cmm_type().value_or(PreferredCMMType { 0 }); - - raw_header.profile_version_major = profile.version().major_version(); - raw_header.profile_version_minor_bugfix = profile.version().minor_and_bugfix_version(); - raw_header.profile_version_zero = 0; - - raw_header.profile_device_class = profile.device_class(); - raw_header.data_color_space = profile.data_color_space(); - raw_header.profile_connection_space = profile.connection_space(); - - DateTime profile_timestamp = profile.creation_timestamp(); - raw_header.profile_creation_time.year = profile_timestamp.year; - raw_header.profile_creation_time.month = profile_timestamp.month; - raw_header.profile_creation_time.day = profile_timestamp.day; - raw_header.profile_creation_time.hours = profile_timestamp.hours; - raw_header.profile_creation_time.minutes = profile_timestamp.minutes; - raw_header.profile_creation_time.seconds = profile_timestamp.seconds; - - raw_header.profile_file_signature = ProfileFileSignature; - raw_header.primary_platform = profile.primary_platform().value_or(PrimaryPlatform { 0 }); - - raw_header.profile_flags = profile.flags().bits(); - raw_header.device_manufacturer = profile.device_manufacturer().value_or(DeviceManufacturer { 0 }); - raw_header.device_model = profile.device_model().value_or(DeviceModel { 0 }); - raw_header.device_attributes = profile.device_attributes().bits(); - raw_header.rendering_intent = profile.rendering_intent(); - - raw_header.pcs_illuminant = profile.pcs_illuminant(); - - raw_header.profile_creator = profile.creator().value_or(Creator { 0 }); - - memset(raw_header.reserved, 0, sizeof(raw_header.reserved)); - - auto id = Profile::compute_id(bytes); - static_assert(sizeof(id.data) == sizeof(raw_header.profile_id)); - memcpy(raw_header.profile_id, id.data, sizeof(id.data)); - - return {}; -} - -ErrorOr encode(Profile const& profile) -{ - // Valid profiles always have tags. Profile only represents valid profiles. - VERIFY(profile.tag_count() > 0); - - HashMap tag_data_map; - Vector tag_data_bytes = TRY(encode_tag_datas(profile, tag_data_map)); - - u32 number_of_serialized_tags = 0; - profile.for_each_tag([&](auto tag_signature, auto tag_data) { - if (!tag_data_map.contains(tag_data.ptr())) { - dbgln("ICC serialization: dropping tag {} because it has unknown type {}", tag_signature, tag_data->type()); - return; - } - number_of_serialized_tags++; - }); - - size_t tag_table_size = sizeof(u32) + number_of_serialized_tags * sizeof(TagTableEntry); - size_t offset = sizeof(ICCHeader) + tag_table_size; - Vector offsets; - for (auto const& bytes : tag_data_bytes) { - TRY(offsets.try_append(offset)); - offset += align_up_to(bytes.size(), 4); - } - - // Include padding after last element. Use create_zeroed() to fill padding bytes with null bytes. - // ICC v4, 7.1.2: - // "c) all tagged element data, including the last, shall be padded by no more than three following pad bytes to - // reach a 4-byte boundary; - // d) all pad bytes shall be NULL (as defined in ISO/IEC 646, character 0/0). - // NOTE 1 This implies that the length is required to be a multiple of four." - auto bytes = TRY(ByteBuffer::create_zeroed(offset)); - - for (size_t i = 0; i < tag_data_bytes.size(); ++i) - memcpy(bytes.data() + offsets[i], tag_data_bytes[i].data(), tag_data_bytes[i].size()); - - TRY(encode_tag_table(bytes, profile, number_of_serialized_tags, offsets, tag_data_bytes, tag_data_map)); - TRY(encode_header(bytes, profile)); - - return bytes; -} - -} diff --git a/Libraries/LibGfx/ICC/BinaryWriter.h b/Libraries/LibGfx/ICC/BinaryWriter.h deleted file mode 100644 index c7d354b9ceef9..0000000000000 --- a/Libraries/LibGfx/ICC/BinaryWriter.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -namespace Gfx::ICC { - -class Profile; - -// Serializes a Profile object. -// Ignores the Profile's on_disk_size() and id() and recomputes them instead. -// Also ignores and the offsets and sizes in tag data. -// But if the profile has its tag data in tag order and has a computed id, -// it's a goal that encode(Profile::try_load_from_externally_owned_memory(bytes) returns `bytes`. -// Unconditionally computes a Profile ID (which is an MD5 hash of most of the contents, see Profile::compute_id()) and writes it to the output. -ErrorOr encode(Profile const&); - -} diff --git a/Libraries/LibGfx/ICC/DistinctFourCC.h b/Libraries/LibGfx/ICC/DistinctFourCC.h deleted file mode 100644 index 34815630c6349..0000000000000 --- a/Libraries/LibGfx/ICC/DistinctFourCC.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Gfx::ICC { - -// The ICC spec uses FourCCs for many different things. -// This is used to give FourCCs for different roles distinct types, so that they can only be compared to the correct constants. -// (FourCCs that have only a small and fixed set of values should use an enum class instead, see e.g. DeviceClass and ColorSpace in Enums.h.) -enum class FourCCType { - PreferredCMMType, - DeviceManufacturer, - DeviceModel, - Creator, - TagSignature, - TagTypeSignature, -}; - -template -struct [[gnu::packed]] DistinctFourCC { - constexpr explicit DistinctFourCC(u32 value) - : value(value) - { - } - constexpr operator u32() const { return value; } - - char c0() const { return value >> 24; } - char c1() const { return (value >> 16) & 0xff; } - char c2() const { return (value >> 8) & 0xff; } - char c3() const { return value & 0xff; } - - bool operator==(DistinctFourCC b) const { return value == b.value; } - - u32 value { 0 }; -}; - -using PreferredCMMType = DistinctFourCC; // ICC v4, "7.2.3 Preferred CMM type field" -using DeviceManufacturer = DistinctFourCC; // ICC v4, "7.2.12 Device manufacturer field" -using DeviceModel = DistinctFourCC; // ICC v4, "7.2.13 Device model field" -using Creator = DistinctFourCC; // ICC v4, "7.2.17 Profile creator field" -using TagSignature = DistinctFourCC; // ICC v4, "9.2 Tag listing" -using TagTypeSignature = DistinctFourCC; // ICC v4, "10 Tag type definitions" - -} - -template -struct AK::Formatter> : StandardFormatter { - ErrorOr format(FormatBuilder& builder, Gfx::ICC::DistinctFourCC const& four_cc) - { - TRY(builder.put_padding('\'', 1)); - TRY(builder.put_padding(four_cc.c0(), 1)); - TRY(builder.put_padding(four_cc.c1(), 1)); - TRY(builder.put_padding(four_cc.c2(), 1)); - TRY(builder.put_padding(four_cc.c3(), 1)); - TRY(builder.put_padding('\'', 1)); - return {}; - } -}; - -template -struct AK::Traits> : public DefaultTraits> { - static unsigned hash(Gfx::ICC::DistinctFourCC const& key) - { - return int_hash(key.value); - } - - static bool equals(Gfx::ICC::DistinctFourCC const& a, Gfx::ICC::DistinctFourCC const& b) - { - return a == b; - } -}; diff --git a/Libraries/LibGfx/ICC/Enums.cpp b/Libraries/LibGfx/ICC/Enums.cpp deleted file mode 100644 index fbcc7c721b665..0000000000000 --- a/Libraries/LibGfx/ICC/Enums.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Gfx::ICC { - -StringView device_class_name(DeviceClass device_class) -{ - switch (device_class) { - case DeviceClass::InputDevice: - return "InputDevice"sv; - case DeviceClass::DisplayDevice: - return "DisplayDevice"sv; - case DeviceClass::OutputDevice: - return "OutputDevice"sv; - case DeviceClass::DeviceLink: - return "DeviceLink"sv; - case DeviceClass::ColorSpace: - return "ColorSpace"sv; - case DeviceClass::Abstract: - return "Abstract"sv; - case DeviceClass::NamedColor: - return "NamedColor"sv; - } - VERIFY_NOT_REACHED(); -} - -StringView data_color_space_name(ColorSpace color_space) -{ - switch (color_space) { - case ColorSpace::nCIEXYZ: - return "nCIEXYZ"sv; - case ColorSpace::CIELAB: - return "CIELAB"sv; - case ColorSpace::CIELUV: - return "CIELUV"sv; - case ColorSpace::YCbCr: - return "YCbCr"sv; - case ColorSpace::CIEYxy: - return "CIEYxy"sv; - case ColorSpace::RGB: - return "RGB"sv; - case ColorSpace::Gray: - return "Gray"sv; - case ColorSpace::HSV: - return "HSV"sv; - case ColorSpace::HLS: - return "HLS"sv; - case ColorSpace::CMYK: - return "CMYK"sv; - case ColorSpace::CMY: - return "CMY"sv; - case ColorSpace::TwoColor: - return "2 color"sv; - case ColorSpace::ThreeColor: - return "3 color (other than XYZ, Lab, Luv, YCbCr, CIEYxy, RGB, HSV, HLS, CMY)"sv; - case ColorSpace::FourColor: - return "4 color (other than CMYK)"sv; - case ColorSpace::FiveColor: - return "5 color"sv; - case ColorSpace::SixColor: - return "6 color"sv; - case ColorSpace::SevenColor: - return "7 color"sv; - case ColorSpace::EightColor: - return "8 color"sv; - case ColorSpace::NineColor: - return "9 color"sv; - case ColorSpace::TenColor: - return "10 color"sv; - case ColorSpace::ElevenColor: - return "11 color"sv; - case ColorSpace::TwelveColor: - return "12 color"sv; - case ColorSpace::ThirteenColor: - return "13 color"sv; - case ColorSpace::FourteenColor: - return "14 color"sv; - case ColorSpace::FifteenColor: - return "15 color"sv; - } - VERIFY_NOT_REACHED(); -} - -StringView profile_connection_space_name(ColorSpace color_space) -{ - switch (color_space) { - case ColorSpace::PCSXYZ: - return "PCSXYZ"sv; - case ColorSpace::PCSLAB: - return "PCSLAB"sv; - default: - return data_color_space_name(color_space); - } -} - -unsigned number_of_components_in_color_space(ColorSpace color_space) -{ - switch (color_space) { - case ColorSpace::Gray: - return 1; - case ColorSpace::TwoColor: - return 2; - case ColorSpace::nCIEXYZ: - case ColorSpace::CIELAB: - case ColorSpace::CIELUV: - case ColorSpace::YCbCr: - case ColorSpace::CIEYxy: - case ColorSpace::RGB: - case ColorSpace::HSV: - case ColorSpace::HLS: - case ColorSpace::CMY: - case ColorSpace::ThreeColor: - return 3; - case ColorSpace::CMYK: - case ColorSpace::FourColor: - return 4; - case ColorSpace::FiveColor: - return 5; - case ColorSpace::SixColor: - return 6; - case ColorSpace::SevenColor: - return 7; - case ColorSpace::EightColor: - return 8; - case ColorSpace::NineColor: - return 9; - case ColorSpace::TenColor: - return 10; - case ColorSpace::ElevenColor: - return 11; - case ColorSpace::TwelveColor: - return 12; - case ColorSpace::ThirteenColor: - return 13; - case ColorSpace::FourteenColor: - return 14; - case ColorSpace::FifteenColor: - return 15; - } - VERIFY_NOT_REACHED(); -} - -StringView primary_platform_name(PrimaryPlatform primary_platform) -{ - switch (primary_platform) { - case PrimaryPlatform::Apple: - return "Apple"sv; - case PrimaryPlatform::Microsoft: - return "Microsoft"sv; - case PrimaryPlatform::SiliconGraphics: - return "Silicon Graphics"sv; - case PrimaryPlatform::Sun: - return "Sun"sv; - } - VERIFY_NOT_REACHED(); -} - -StringView rendering_intent_name(RenderingIntent rendering_intent) -{ - switch (rendering_intent) { - case RenderingIntent::Perceptual: - return "Perceptual"sv; - case RenderingIntent::MediaRelativeColorimetric: - return "Media-relative colorimetric"sv; - case RenderingIntent::Saturation: - return "Saturation"sv; - case RenderingIntent::ICCAbsoluteColorimetric: - return "ICC-absolute colorimetric"sv; - } - VERIFY_NOT_REACHED(); -} - -} diff --git a/Libraries/LibGfx/ICC/Enums.h b/Libraries/LibGfx/ICC/Enums.h deleted file mode 100644 index 1c298dbe9a2a2..0000000000000 --- a/Libraries/LibGfx/ICC/Enums.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Gfx::ICC { - -// ICC v4, 7.2.5 Profile/device class field -enum class DeviceClass : u32 { - InputDevice = 0x73636E72, // 'scnr' - DisplayDevice = 0x6D6E7472, // 'mntr' - OutputDevice = 0x70727472, // 'prtr' - DeviceLink = 0x6C696E6B, // 'link' - ColorSpace = 0x73706163, // 'spac' - Abstract = 0x61627374, // 'abst' - NamedColor = 0x6E6D636C, // 'nmcl' -}; -StringView device_class_name(DeviceClass); - -// ICC v4, 7.2.6 Data colour space field, Table 19 — Data colour space signatures -enum class ColorSpace : u32 { - nCIEXYZ = 0x58595A20, // 'XYZ ', used in data color spaces. - PCSXYZ = nCIEXYZ, // Used in profile connection space instead. - CIELAB = 0x4C616220, // 'Lab ', used in data color spaces. - PCSLAB = CIELAB, // Used in profile connection space instead. - CIELUV = 0x4C757620, // 'Luv ' - YCbCr = 0x59436272, // 'YCbr' - CIEYxy = 0x59787920, // 'Yxy ' - RGB = 0x52474220, // 'RGB ' - Gray = 0x47524159, // 'GRAY' - HSV = 0x48535620, // 'HSV ' - HLS = 0x484C5320, // 'HLS ' - CMYK = 0x434D594B, // 'CMYK' - CMY = 0x434D5920, // 'CMY ' - TwoColor = 0x32434C52, // '2CLR' - ThreeColor = 0x33434C52, // '3CLR' - FourColor = 0x34434C52, // '4CLR' - FiveColor = 0x35434C52, // '5CLR' - SixColor = 0x36434C52, // '6CLR' - SevenColor = 0x37434C52, // '7CLR' - EightColor = 0x38434C52, // '8CLR' - NineColor = 0x39434C52, // '9CLR' - TenColor = 0x41434C52, // 'ACLR' - ElevenColor = 0x42434C52, // 'BCLR' - TwelveColor = 0x43434C52, // 'CCLR' - ThirteenColor = 0x44434C52, // 'DCLR' - FourteenColor = 0x45434C52, // 'ECLR' - FifteenColor = 0x46434C52, // 'FCLR' -}; -StringView data_color_space_name(ColorSpace); -StringView profile_connection_space_name(ColorSpace); -unsigned number_of_components_in_color_space(ColorSpace); - -// ICC v4, 7.2.10 Primary platform field, Table 20 — Primary platforms -enum class PrimaryPlatform : u32 { - Apple = 0x4150504C, // 'APPL' - Microsoft = 0x4D534654, // 'MSFT' - SiliconGraphics = 0x53474920, // 'SGI ' - Sun = 0x53554E57, // 'SUNW' -}; -StringView primary_platform_name(PrimaryPlatform); - -// ICC v4, 7.2.15 Rendering intent field -enum class RenderingIntent { - Perceptual, - MediaRelativeColorimetric, - Saturation, - ICCAbsoluteColorimetric, -}; -StringView rendering_intent_name(RenderingIntent); - -} diff --git a/Libraries/LibGfx/ICC/Profile.cpp b/Libraries/LibGfx/ICC/Profile.cpp deleted file mode 100644 index b4d66b6357842..0000000000000 --- a/Libraries/LibGfx/ICC/Profile.cpp +++ /dev/null @@ -1,1812 +0,0 @@ -/* - * Copyright (c) 2022-2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// V2 spec: https://color.org/specification/ICC.1-2001-04.pdf -// V4 spec: https://color.org/specification/ICC.1-2022-05.pdf - -namespace Gfx::ICC { - -namespace { - -ErrorOr parse_date_time_number(DateTimeNumber const& date_time) -{ - return DateTime { - .year = date_time.year, - .month = date_time.month, - .day = date_time.day, - .hours = date_time.hours, - .minutes = date_time.minutes, - .seconds = date_time.seconds, - }; -} - -ErrorOr parse_size(ICCHeader const& header, ReadonlyBytes icc_bytes) -{ - // ICC v4, 7.2.2 Profile size field - // "The value in the profile size field shall be the exact size obtained by combining the profile header, - // the tag table, and the tagged element data, including the pad bytes for the last tag." - - // Valid files have enough data for profile header and tag table entry count. - if (header.profile_size < sizeof(ICCHeader) + sizeof(u32)) - return Error::from_string_literal("ICC::Profile: Profile size too small"); - - if (header.profile_size > icc_bytes.size()) - return Error::from_string_literal("ICC::Profile: Profile size larger than input data"); - - // ICC v4, 7.1.2: - // "NOTE 1 This implies that the length is required to be a multiple of four." - // The ICC v2 spec doesn't have this note. It instead has: - // ICC v2, 6.2.2 Offset: - // "All tag data is required to start on a 4-byte boundary" - // And indeed, there are files in the wild where the last tag has a size that isn't a multiple of four, - // resulting in an ICC file whose size isn't a multiple of four either. - if (header.profile_version_major >= 4 && header.profile_size % 4 != 0) - return Error::from_string_literal("ICC::Profile: Profile size not a multiple of four"); - - return header.profile_size; -} - -Optional parse_preferred_cmm_type(ICCHeader const& header) -{ - // ICC v4, 7.2.3 Preferred CMM type field - - // "This field may be used to identify the preferred CMM to be used. - // If used, it shall match a CMM type signature registered in the ICC Tag Registry" - // https://www.color.org/signatures2.xalter currently links to - // https://www.color.org/registry/signature/TagRegistry-2021-03.pdf, which contains - // some CMM signatures. - // This requirement is often honored in practice, but not always. For example, - // JPEGs exported in Adobe Lightroom contain profiles that set this to 'Lino', - // which is not present in the "CMM Signatures" table in that PDF. - - // "If no preferred CMM is identified, this field shall be set to zero (00000000h)." - if (header.preferred_cmm_type == PreferredCMMType { 0 }) - return {}; - return header.preferred_cmm_type; -} - -ErrorOr parse_version(ICCHeader const& header) -{ - // ICC v4, 7.2.4 Profile version field - if (header.profile_version_zero != 0) - return Error::from_string_literal("ICC::Profile: Reserved version bytes not zero"); - return Version(header.profile_version_major, header.profile_version_minor_bugfix); -} - -ErrorOr parse_device_class(ICCHeader const& header) -{ - // ICC v4, 7.2.5 Profile/device class field - switch (header.profile_device_class) { - case DeviceClass::InputDevice: - case DeviceClass::DisplayDevice: - case DeviceClass::OutputDevice: - case DeviceClass::DeviceLink: - case DeviceClass::ColorSpace: - case DeviceClass::Abstract: - case DeviceClass::NamedColor: - return header.profile_device_class; - } - return Error::from_string_literal("ICC::Profile: Invalid device class"); -} - -ErrorOr parse_color_space(ColorSpace color_space) -{ - // ICC v4, Table 19 — Data colour space signatures - switch (color_space) { - case ColorSpace::nCIEXYZ: - case ColorSpace::CIELAB: - case ColorSpace::CIELUV: - case ColorSpace::YCbCr: - case ColorSpace::CIEYxy: - case ColorSpace::RGB: - case ColorSpace::Gray: - case ColorSpace::HSV: - case ColorSpace::HLS: - case ColorSpace::CMYK: - case ColorSpace::CMY: - case ColorSpace::TwoColor: - case ColorSpace::ThreeColor: - case ColorSpace::FourColor: - case ColorSpace::FiveColor: - case ColorSpace::SixColor: - case ColorSpace::SevenColor: - case ColorSpace::EightColor: - case ColorSpace::NineColor: - case ColorSpace::TenColor: - case ColorSpace::ElevenColor: - case ColorSpace::TwelveColor: - case ColorSpace::ThirteenColor: - case ColorSpace::FourteenColor: - case ColorSpace::FifteenColor: - return color_space; - } - return Error::from_string_literal("ICC::Profile: Invalid color space"); -} - -ErrorOr parse_data_color_space(ICCHeader const& header) -{ - // ICC v4, 7.2.6 Data colour space field - return parse_color_space(header.data_color_space); -} - -ErrorOr parse_connection_space(ICCHeader const& header) -{ - // ICC v4, 7.2.7 PCS field - // and Annex D - auto space = TRY(parse_color_space(header.profile_connection_space)); - - if (header.profile_device_class != DeviceClass::DeviceLink && (space != ColorSpace::PCSXYZ && space != ColorSpace::PCSLAB)) - return Error::from_string_literal("ICC::Profile: Invalid profile connection space: Non-PCS space on non-DeviceLink profile"); - - return space; -} - -ErrorOr parse_creation_date_time(ICCHeader const& header) -{ - // ICC v4, 7.2.8 Date and time field - return parse_date_time_number(header.profile_creation_time); -} - -ErrorOr parse_file_signature(ICCHeader const& header) -{ - // ICC v4, 7.2.9 Profile file signature field - if (header.profile_file_signature != ProfileFileSignature) - return Error::from_string_literal("ICC::Profile: profile file signature not 'acsp'"); - return {}; -} - -ErrorOr> parse_primary_platform(ICCHeader const& header) -{ - // ICC v4, 7.2.10 Primary platform field - // "If there is no primary platform identified, this field shall be set to zero (00000000h)." - if (header.primary_platform == PrimaryPlatform { 0 }) - return OptionalNone {}; - - switch (header.primary_platform) { - case PrimaryPlatform::Apple: - case PrimaryPlatform::Microsoft: - case PrimaryPlatform::SiliconGraphics: - case PrimaryPlatform::Sun: - return header.primary_platform; - } - return Error::from_string_literal("ICC::Profile: Invalid primary platform"); -} - -Optional parse_device_manufacturer(ICCHeader const& header) -{ - // ICC v4, 7.2.12 Device manufacturer field - // "This field may be used to identify a device manufacturer. - // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org" - // Device manufacturers can be looked up at https://www.color.org/signatureRegistry/index.xalter - // For example: https://www.color.org/signatureRegistry/?entityEntry=APPL-4150504C - // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter - // has its device manufacturer set to 'none', but https://www.color.org/signatureRegistry/?entityEntry=none-6E6F6E65 does not exist. - - // "If not used this field shall be set to zero (00000000h)." - if (header.device_manufacturer == DeviceManufacturer { 0 }) - return {}; - return header.device_manufacturer; -} - -Optional parse_device_model(ICCHeader const& header) -{ - // ICC v4, 7.2.13 Device model field - // "This field may be used to identify a device model. - // If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org" - // Device models can be looked up at https://www.color.org/signatureRegistry/deviceRegistry/index.xalter - // For example: https://www.color.org/signatureRegistry/deviceRegistry/?entityEntry=7FD8-37464438 - // Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter - // has its device model set to 'none', but https://www.color.org/signatureRegistry/deviceRegistry?entityEntry=none-6E6F6E65 does not exist. - - // "If not used this field shall be set to zero (00000000h)." - if (header.device_model == DeviceModel { 0 }) - return {}; - return header.device_model; -} - -ErrorOr parse_device_attributes(ICCHeader const& header) -{ - // ICC v4, 7.2.14 Device attributes field - - // "4 to 31": "Reserved (set to binary zero)" - if (header.device_attributes & 0xffff'fff0) - return Error::from_string_literal("ICC::Profile: Device attributes reserved bits not set to 0"); - - return DeviceAttributes { header.device_attributes }; -} - -ErrorOr parse_rendering_intent(ICCHeader const& header) -{ - // ICC v4, 7.2.15 Rendering intent field - switch (header.rendering_intent) { - case RenderingIntent::Perceptual: - case RenderingIntent::MediaRelativeColorimetric: - case RenderingIntent::Saturation: - case RenderingIntent::ICCAbsoluteColorimetric: - return header.rendering_intent; - } - return Error::from_string_literal("ICC::Profile: Invalid rendering intent"); -} - -ErrorOr parse_pcs_illuminant(ICCHeader const& header) -{ - // ICC v4, 7.2.16 PCS illuminant field - XYZ xyz = (XYZ)header.pcs_illuminant; - - /// "The value, when rounded to four decimals, shall be X = 0,9642, Y = 1,0 and Z = 0,8249." - if (round(xyz.X * 10'000) != 9'642 || round(xyz.Y * 10'000) != 10'000 || round(xyz.Z * 10'000) != 8'249) - return Error::from_string_literal("ICC::Profile: Invalid pcs illuminant"); - - return xyz; -} - -Optional parse_profile_creator(ICCHeader const& header) -{ - // ICC v4, 7.2.17 Profile creator field - // "This field may be used to identify the creator of the profile. - // If used the signature should match the signature contained in the device manufacturer section of the ICC signature registry found at www.color.org." - // This is not always true in practice. - // For example, .icc files in /System/ColorSync/Profiles on macOS 12.6 set this to 'appl', which is a CMM signature, not a device signature (that one would be 'APPL'). - - // "If not used this field shall be set to zero (00000000h)." - if (header.profile_creator == Creator { 0 }) - return {}; - return header.profile_creator; -} - -template -bool all_bytes_are_zero(u8 const (&bytes)[N]) -{ - for (u8 byte : bytes) { - if (byte != 0) - return false; - } - return true; -} - -ErrorOr> parse_profile_id(ICCHeader const& header, ReadonlyBytes icc_bytes) -{ - // ICC v4, 7.2.18 Profile ID field - // "A profile ID field value of zero (00h) shall indicate that a profile ID has not been calculated." - if (all_bytes_are_zero(header.profile_id)) - return OptionalNone {}; - - Crypto::Hash::MD5::DigestType id; - static_assert(sizeof(id.data) == sizeof(header.profile_id)); - memcpy(id.data, header.profile_id, sizeof(id.data)); - - auto computed_id = Profile::compute_id(icc_bytes); - if (id != computed_id) - return Error::from_string_literal("ICC::Profile: Invalid profile id"); - - return id; -} - -ErrorOr parse_reserved(ICCHeader const& header) -{ - // ICC v4, 7.2.19 Reserved field - // "This field of the profile header is reserved for future ICC definition and shall be set to zero." - if (!all_bytes_are_zero(header.reserved)) - return Error::from_string_literal("ICC::Profile: Reserved header bytes are not zero"); - return {}; -} -} - -URL::URL device_manufacturer_url(DeviceManufacturer device_manufacturer) -{ - return URL::URL(ByteString::formatted("https://www.color.org/signatureRegistry/?entityEntry={:c}{:c}{:c}{:c}-{:08X}", - device_manufacturer.c0(), device_manufacturer.c1(), device_manufacturer.c2(), device_manufacturer.c3(), device_manufacturer.value)); -} - -URL::URL device_model_url(DeviceModel device_model) -{ - return URL::URL(ByteString::formatted("https://www.color.org/signatureRegistry/deviceRegistry/?entityEntry={:c}{:c}{:c}{:c}-{:08X}", - device_model.c0(), device_model.c1(), device_model.c2(), device_model.c3(), device_model.value)); -} - -Flags::Flags() = default; -Flags::Flags(u32 bits) - : m_bits(bits) -{ -} - -DeviceAttributes::DeviceAttributes() = default; -DeviceAttributes::DeviceAttributes(u64 bits) - : m_bits(bits) -{ -} - -static ErrorOr validate_date_time(DateTime const& date_time) -{ - // Returns if a DateTime is valid per ICC V4, 4.2 dateTimeNumber. - // In practice, some profiles contain invalid dates, but we should enforce this for data we write at least. - - // "Number of the month (1 to 12)" - if (date_time.month < 1 || date_time.month > 12) - return Error::from_string_literal("ICC::Profile: dateTimeNumber month out of bounds"); - - // "Number of the day of the month (1 to 31)" - if (date_time.day < 1 || date_time.day > 31) - return Error::from_string_literal("ICC::Profile: dateTimeNumber day out of bounds"); - - // "Number of hours (0 to 23)" - if (date_time.hours > 23) - return Error::from_string_literal("ICC::Profile: dateTimeNumber hours out of bounds"); - - // "Number of minutes (0 to 59)" - if (date_time.minutes > 59) - return Error::from_string_literal("ICC::Profile: dateTimeNumber minutes out of bounds"); - - // "Number of seconds (0 to 59)" - // ICC profiles apparently can't be created during leap seconds (seconds would be 60 there, but the spec doesn't allow that). - if (date_time.seconds > 59) - return Error::from_string_literal("ICC::Profile: dateTimeNumber seconds out of bounds"); - - return {}; -} - -ErrorOr DateTime::to_time_t() const -{ - TRY(validate_date_time(*this)); - - struct tm tm = {}; - tm.tm_year = year - 1900; - tm.tm_mon = month - 1; - tm.tm_mday = day; - tm.tm_hour = hours; - tm.tm_min = minutes; - tm.tm_sec = seconds; - // timegm() doesn't read tm.tm_isdst, tm.tm_wday, and tm.tm_yday, no need to fill them in. - - time_t timestamp = timegm(&tm); - if (timestamp == -1) - return Error::from_string_literal("ICC::Profile: dateTimeNumber not representable as timestamp"); - - return timestamp; -} - -ErrorOr DateTime::from_time_t(time_t timestamp) -{ - struct tm gmt_tm; - if (gmtime_r(×tamp, &gmt_tm) == NULL) - return Error::from_string_literal("ICC::Profile: timestamp not representable as DateTimeNumber"); - - // FIXME: Range-check, using something like `TRY(Checked(x).try_value())`? - DateTime result { - .year = static_cast(gmt_tm.tm_year + 1900), - .month = static_cast(gmt_tm.tm_mon + 1), - .day = static_cast(gmt_tm.tm_mday), - .hours = static_cast(gmt_tm.tm_hour), - .minutes = static_cast(gmt_tm.tm_min), - .seconds = static_cast(gmt_tm.tm_sec), - }; - TRY(validate_date_time(result)); - return result; -} - -static ErrorOr read_header(ReadonlyBytes bytes) -{ - if (bytes.size() < sizeof(ICCHeader)) - return Error::from_string_literal("ICC::Profile: Not enough data for header"); - - ProfileHeader header; - auto raw_header = *bit_cast(bytes.data()); - - TRY(parse_file_signature(raw_header)); - header.on_disk_size = TRY(parse_size(raw_header, bytes)); - header.preferred_cmm_type = parse_preferred_cmm_type(raw_header); - header.version = TRY(parse_version(raw_header)); - header.device_class = TRY(parse_device_class(raw_header)); - header.data_color_space = TRY(parse_data_color_space(raw_header)); - header.connection_space = TRY(parse_connection_space(raw_header)); - header.creation_timestamp = TRY(parse_creation_date_time(raw_header)); - header.primary_platform = TRY(parse_primary_platform(raw_header)); - header.flags = Flags { raw_header.profile_flags }; - header.device_manufacturer = parse_device_manufacturer(raw_header); - header.device_model = parse_device_model(raw_header); - header.device_attributes = TRY(parse_device_attributes(raw_header)); - header.rendering_intent = TRY(parse_rendering_intent(raw_header)); - header.pcs_illuminant = TRY(parse_pcs_illuminant(raw_header)); - header.creator = parse_profile_creator(raw_header); - header.id = TRY(parse_profile_id(raw_header, bytes)); - TRY(parse_reserved(raw_header)); - - return header; -} - -static ErrorOr> read_tag(ReadonlyBytes bytes, u32 offset_to_beginning_of_tag_data_element, u32 size_of_tag_data_element) -{ - // "All tag data elements shall start on a 4-byte boundary (relative to the start of the profile data stream)" - if (offset_to_beginning_of_tag_data_element % 4 != 0) - return Error::from_string_literal("ICC::Profile: Tag data not aligned"); - - if (static_cast(offset_to_beginning_of_tag_data_element) + size_of_tag_data_element > bytes.size()) - return Error::from_string_literal("ICC::Profile: Tag data out of bounds"); - - auto tag_bytes = bytes.slice(offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - - // ICC v4, 9 Tag definitions - // ICC v4, 9.1 General - // "All tags, including private tags, have as their first four bytes a tag signature to identify to profile readers - // what kind of data is contained within a tag." - if (tag_bytes.size() < sizeof(u32)) - return Error::from_string_literal("ICC::Profile: Not enough data for tag type"); - - auto type = tag_type(tag_bytes); - switch (type) { - case ChromaticityTagData::Type: - return ChromaticityTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case CicpTagData::Type: - return CicpTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case CurveTagData::Type: - return CurveTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case Lut16TagData::Type: - return Lut16TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case Lut8TagData::Type: - return Lut8TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case LutAToBTagData::Type: - return LutAToBTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case LutBToATagData::Type: - return LutBToATagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case MeasurementTagData::Type: - return MeasurementTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case MultiLocalizedUnicodeTagData::Type: - return MultiLocalizedUnicodeTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case NamedColor2TagData::Type: - return NamedColor2TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case ParametricCurveTagData::Type: - return ParametricCurveTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case S15Fixed16ArrayTagData::Type: - return S15Fixed16ArrayTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case SignatureTagData::Type: - return SignatureTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case TextDescriptionTagData::Type: - return TextDescriptionTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case TextTagData::Type: - return TextTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case ViewingConditionsTagData::Type: - return ViewingConditionsTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - case XYZTagData::Type: - return XYZTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); - default: - // FIXME: optionally ignore tags of unknown type - return try_make_ref_counted(offset_to_beginning_of_tag_data_element, size_of_tag_data_element, type); - } -} - -static ErrorOr>> read_tag_table(ReadonlyBytes bytes) -{ - OrderedHashMap> tag_table; - - // ICC v4, 7.3 Tag table - // ICC v4, 7.3.1 Overview - // "The tag table acts as a table of contents for the tags and an index into the tag data element in the profiles. It - // shall consist of a 4-byte entry that contains a count of the number of tags in the table followed by a series of 12- - // byte entries with one entry for each tag. The tag table therefore contains 4+12n bytes where n is the number of - // tags contained in the profile. The entries for the tags within the table are not required to be in any particular - // order nor are they required to match the sequence of tag data element within the profile. - // Each 12-byte tag entry following the tag count shall consist of a 4-byte tag signature, a 4-byte offset to define - // the beginning of the tag data element, and a 4-byte entry identifying the length of the tag data element in bytes. - // [...] - // The tag table shall define a contiguous sequence of unique tag elements, with no gaps between the last byte - // of any tag data element referenced from the tag table (inclusive of any necessary additional pad bytes required - // to reach a four-byte boundary) and the byte offset of the following tag element, or the end of the file. - // Duplicate tag signatures shall not be included in the tag table. - // Tag data elements shall not partially overlap, so there shall be no part of any tag data element that falls within - // the range defined for another tag in the tag table." - - ReadonlyBytes tag_table_bytes = bytes.slice(sizeof(ICCHeader)); - - if (tag_table_bytes.size() < sizeof(u32)) - return Error::from_string_literal("ICC::Profile: Not enough data for tag count"); - auto tag_count = *bit_cast const*>(tag_table_bytes.data()); - - tag_table_bytes = tag_table_bytes.slice(sizeof(u32)); - if (tag_table_bytes.size() < tag_count * sizeof(TagTableEntry)) - return Error::from_string_literal("ICC::Profile: Not enough data for tag table entries"); - auto tag_table_entries = bit_cast(tag_table_bytes.data()); - - // "The tag table may contain multiple tags signatures that all reference the same tag data element offset, allowing - // efficient reuse of tag data elements." - HashMap> offset_to_tag_data; - - for (u32 i = 0; i < tag_count; ++i) { - // FIXME: optionally ignore tags with unknown signature - - // Dedupe identical offset/sizes. - NonnullRefPtr tag_data = TRY(offset_to_tag_data.try_ensure(tag_table_entries[i].offset_to_beginning_of_tag_data_element, [&]() { - return read_tag(bytes, tag_table_entries[i].offset_to_beginning_of_tag_data_element, tag_table_entries[i].size_of_tag_data_element); - })); - - // "In such cases, both the offset and size of the tag data elements in the tag table shall be the same." - if (tag_data->size() != tag_table_entries[i].size_of_tag_data_element) - return Error::from_string_literal("ICC::Profile: two tags have same offset but different sizes"); - - // "Duplicate tag signatures shall not be included in the tag table." - if (TRY(tag_table.try_set(tag_table_entries[i].tag_signature, move(tag_data))) != AK::HashSetResult::InsertedNewEntry) - return Error::from_string_literal("ICC::Profile: duplicate tag signature"); - } - - return tag_table; -} - -static bool is_xCLR(ColorSpace color_space) -{ - switch (color_space) { - case ColorSpace::TwoColor: - case ColorSpace::ThreeColor: - case ColorSpace::FourColor: - case ColorSpace::FiveColor: - case ColorSpace::SixColor: - case ColorSpace::SevenColor: - case ColorSpace::EightColor: - case ColorSpace::NineColor: - case ColorSpace::TenColor: - case ColorSpace::ElevenColor: - case ColorSpace::TwelveColor: - case ColorSpace::ThirteenColor: - case ColorSpace::FourteenColor: - case ColorSpace::FifteenColor: - return true; - default: - return false; - } -} - -ErrorOr Profile::check_required_tags() -{ - // ICC v4, 8 Required tags - - // ICC v4, 8.2 Common requirements - // "With the exception of DeviceLink profiles, all profiles shall contain the following tags: - // - profileDescriptionTag (see 9.2.41); - // - copyrightTag (see 9.2.21); - // - mediaWhitePointTag (see 9.2.34); - // - chromaticAdaptationTag, when the measurement data used to calculate the profile was specified for an - // adopted white with a chromaticity different from that of the PCS adopted white (see 9.2.15). - // NOTE A DeviceLink profile is not required to have either a mediaWhitePointTag or a chromaticAdaptationTag." - // profileDescriptionTag, copyrightTag are required for DeviceLink too (see ICC v4, 8.6 DeviceLink profile). - // profileDescriptionTag, copyrightTag, mediaWhitePointTag are required in ICC v2 as well. - // chromaticAdaptationTag isn't required in v2 profiles as far as I can tell. - - if (!m_tag_table.contains(profileDescriptionTag)) - return Error::from_string_literal("ICC::Profile: required profileDescriptionTag is missing"); - - if (!m_tag_table.contains(copyrightTag)) - return Error::from_string_literal("ICC::Profile: required copyrightTag is missing"); - - if (device_class() != DeviceClass::DeviceLink) { - if (!m_tag_table.contains(mediaWhitePointTag)) - return Error::from_string_literal("ICC::Profile: required mediaWhitePointTag is missing"); - - // FIXME: Check for chromaticAdaptationTag after figuring out when exactly it needs to be present. - } - - auto has_tag = [&](auto& tag) { return m_tag_table.contains(tag); }; - auto has_all_tags = [&](T tags) { return all_of(tags, has_tag); }; - - switch (device_class()) { - case DeviceClass::InputDevice: { - // ICC v4, 8.3 Input profiles - // "8.3.1 General - // Input profiles are generally used with devices such as scanners and digital cameras. The types of profiles - // available for use as Input profiles are N-component LUT-based, Three-component matrix-based, and - // monochrome. - // 8.3.2 N-component LUT-based Input profiles - // In addition to the tags listed in 8.2 an N-component LUT-based Input profile shall contain the following tag: - // - AToB0Tag (see 9.2.1). - // 8.3.3 Three-component matrix-based Input profiles - // In addition to the tags listed in 8.2, a three-component matrix-based Input profile shall contain the following tags: - // - redMatrixColumnTag (see 9.2.44); - // - greenMatrixColumnTag (see 9.2.30); - // - blueMatrixColumnTag (see 9.2.4); - // - redTRCTag (see 9.2.45); - // - greenTRCTag (see 9.2.31); - // - blueTRCTag (see 9.2.5). - // [...] Only the PCSXYZ encoding can be used with matrix/TRC models. - // 8.3.4 Monochrome Input profiles - // In addition to the tags listed in 8.2, a monochrome Input profile shall contain the following tag: - // - grayTRCTag (see 9.2.29)." - bool has_n_component_lut_based_tags = has_tag(AToB0Tag); - bool has_three_component_matrix_based_tags = has_all_tags(Array { redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag, redTRCTag, greenTRCTag, blueTRCTag }); - bool has_monochrome_tags = has_tag(grayTRCTag); - if (!has_n_component_lut_based_tags && !has_three_component_matrix_based_tags && !has_monochrome_tags) - return Error::from_string_literal("ICC::Profile: InputDevice required tags are missing"); - if (!has_n_component_lut_based_tags && has_three_component_matrix_based_tags && connection_space() != ColorSpace::PCSXYZ) - return Error::from_string_literal("ICC::Profile: InputDevice three-component matrix-based profile must use PCSXYZ"); - break; - } - case DeviceClass::DisplayDevice: { - // ICC v4, 8.4 Display profiles - // "8.4.1 General - // This class of profiles represents display devices such as monitors. The types of profiles available for use as - // Display profiles are N-component LUT-based, Three-component matrix-based, and monochrome. - // 8.4.2 N-Component LUT-based Display profiles - // In addition to the tags listed in 8.2 an N-component LUT-based Input profile shall contain the following tags: - // - AToB0Tag (see 9.2.1); - // - BToA0Tag (see 9.2.6). - // 8.4.3 Three-component matrix-based Display profiles - // In addition to the tags listed in 8.2, a three-component matrix-based Display profile shall contain the following - // tags: - // - redMatrixColumnTag (see 9.2.44); - // - greenMatrixColumnTag (see 9.2.30); - // - blueMatrixColumnTag (see 9.2.4); - // - redTRCTag (see 9.2.45); - // - greenTRCTag (see 9.2.31); - // - blueTRCTag (see 9.2.5). - // [...] Only the PCSXYZ encoding can be used with matrix/TRC models. - // 8.4.4 Monochrome Display profiles - // In addition to the tags listed in 8.2 a monochrome Display profile shall contain the following tag: - // - grayTRCTag (see 9.2.29)." - bool has_n_component_lut_based_tags = has_all_tags(Array { AToB0Tag, BToA0Tag }); - bool has_three_component_matrix_based_tags = has_all_tags(Array { redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag, redTRCTag, greenTRCTag, blueTRCTag }); - bool has_monochrome_tags = has_tag(grayTRCTag); - if (!has_n_component_lut_based_tags && !has_three_component_matrix_based_tags && !has_monochrome_tags) - return Error::from_string_literal("ICC::Profile: DisplayDevice required tags are missing"); - if (!has_n_component_lut_based_tags && has_three_component_matrix_based_tags && connection_space() != ColorSpace::PCSXYZ) - return Error::from_string_literal("ICC::Profile: DisplayDevice three-component matrix-based profile must use PCSXYZ"); - break; - } - case DeviceClass::OutputDevice: { - // ICC v4, 8.5 Output profiles - // "8.5.1 General - // Output profiles are used to support devices such as printers and film recorders. The types of profiles available - // for use as Output profiles are N-component LUT-based and Monochrome. - // 8.5.2 N-component LUT-based Output profiles - // In addition to the tags listed in 8.2 an N-component LUT-based Output profile shall contain the following tags: - // - AToB0Tag (see 9.2.1); - // - AToB1Tag (see 9.2.2); - // - AToB2Tag (see 9.2.3); - // - BToA0Tag (see 9.2.6); - // - BToA1Tag (see 9.2.7); - // - BToA2Tag (see 9.2.8); - // - gamutTag (see 9.2.28); - // - colorantTableTag (see 9.2.18), for the xCLR colour spaces (see 7.2.6) - // 8.5.3 Monochrome Output profiles - // In addition to the tags listed in 8.2 a monochrome Output profile shall contain the following tag: - // - grayTRCTag (see 9.2.29)." - // The colorantTableTag requirement is new in v4. - Vector required_n_component_lut_based_tags = { AToB0Tag, AToB1Tag, AToB2Tag, BToA0Tag, BToA1Tag, BToA2Tag, gamutTag }; - if (is_v4() && is_xCLR(connection_space())) - required_n_component_lut_based_tags.append(colorantTableTag); - bool has_n_component_lut_based_tags = has_all_tags(required_n_component_lut_based_tags); - bool has_monochrome_tags = has_tag(grayTRCTag); - if (!has_n_component_lut_based_tags && !has_monochrome_tags) - return Error::from_string_literal("ICC::Profile: OutputDevice required tags are missing"); - break; - } - case DeviceClass::DeviceLink: { - // ICC v4, 8.6 DeviceLink profile - // "A DeviceLink profile shall contain the following tags: - // - profileDescriptionTag (see 9.2.41); - // - copyrightTag (see 9.2.21); - // - profileSequenceDescTag (see 9.2.42); - // - AToB0Tag (see 9.2.1); - // - colorantTableTag (see 9.2.18) which is required only if the data colour space field is xCLR, where x is - // hexadecimal 2 to F (see 7.2.6); - // - colorantTableOutTag (see 9.2.19), required only if the PCS field is xCLR, where x is hexadecimal 2 to F - // (see 7.2.6)" - // profileDescriptionTag and copyrightTag are already checked above, in the code for section 8.2. - Vector required_tags = { profileSequenceDescTag, AToB0Tag }; - if (is_v4() && is_xCLR(connection_space())) { // This requirement is new in v4. - required_tags.append(colorantTableTag); - required_tags.append(colorantTableOutTag); - } - if (!has_all_tags(required_tags)) - return Error::from_string_literal("ICC::Profile: DeviceLink required tags are missing"); - // "The data colour space field (see 7.2.6) in the DeviceLink profile will be the same as the data colour space field - // of the first profile in the sequence used to construct the device link. The PCS field (see 7.2.7) will be the same - // as the data colour space field of the last profile in the sequence." - // FIXME: Check that if profileSequenceDescType parsing is implemented. - break; - } - case DeviceClass::ColorSpace: - // ICC v4, 8.7 ColorSpace profile - // "In addition to the tags listed in 8.2, a ColorSpace profile shall contain the following tags: - // - BToA0Tag (see 9.2.6); - // - AToB0Tag (see 9.2.1). - // [...] ColorSpace profiles may be embedded in images." - if (!has_all_tags(Array { AToB0Tag, BToA0Tag })) - return Error::from_string_literal("ICC::Profile: ColorSpace required tags are missing"); - break; - case DeviceClass::Abstract: - // ICC v4, 8.8 Abstract profile - // "In addition to the tags listed in 8.2, an Abstract profile shall contain the following tag: - // - AToB0Tag (see 9.2.1). - // [...] Abstract profiles cannot be embedded in images." - if (!has_tag(AToB0Tag)) - return Error::from_string_literal("ICC::Profile: Abstract required AToB0Tag is missing"); - break; - case DeviceClass::NamedColor: - // ICC v4, 8.9 NamedColor profile - // "In addition to the tags listed in 8.2, a NamedColor profile shall contain the following tag: - // - namedColor2Tag (see 9.2.35)." - if (!has_tag(namedColor2Tag)) - return Error::from_string_literal("ICC::Profile: NamedColor required namedColor2Tag is missing"); - break; - } - - m_cached_has_any_a_to_b_tag = has_tag(AToB0Tag) || has_tag(AToB1Tag) || has_tag(AToB2Tag); - m_cached_has_a_to_b0_tag = has_tag(AToB0Tag); - m_cached_has_any_b_to_a_tag = has_tag(BToA0Tag) || has_tag(BToA1Tag) || has_tag(BToA2Tag); - m_cached_has_b_to_a0_tag = has_tag(BToA0Tag); - m_cached_has_all_rgb_matrix_tags = has_all_tags(Array { redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag, redTRCTag, greenTRCTag, blueTRCTag }); - - return {}; -} - -ErrorOr Profile::check_tag_types() -{ - // This uses m_tag_table.get() even for tags that are guaranteed to exist after check_required_tags() - // so that the two functions can be called in either order. - - // Profile ID of /System/Library/ColorSync/Profiles/ITU-2020.icc on macOS 13.1. - static constexpr Crypto::Hash::MD5::DigestType apple_itu_2020_id = { 0x57, 0x0b, 0x1b, 0x76, 0xc6, 0xa0, 0x50, 0xaa, 0x9f, 0x6c, 0x53, 0x8d, 0xbe, 0x2d, 0x3e, 0xf0 }; - - // Profile ID of the "Display P3" profiles embedded in the images on https://webkit.org/blog-files/color-gamut/comparison.html - // (The macOS 13.1 /System/Library/ColorSync/Profiles/Display\ P3.icc file no longer has this quirk.) - static constexpr Crypto::Hash::MD5::DigestType apple_p3_2015_id = { 0xe5, 0xbb, 0x0e, 0x98, 0x67, 0xbd, 0x46, 0xcd, 0x4b, 0xbe, 0x44, 0x6e, 0xbd, 0x1b, 0x75, 0x98 }; - - // Profile ID of the "Display P3" profile in object 881 in https://fredrikbk.com/publications/copy-and-patch.pdf - // (The macOS 13.1 /System/Library/ColorSync/Profiles/Display\ P3.icc file no longer has this quirk.) - static constexpr Crypto::Hash::MD5::DigestType apple_p3_2017_id = { 0xca, 0x1a, 0x95, 0x82, 0x25, 0x7f, 0x10, 0x4d, 0x38, 0x99, 0x13, 0xd5, 0xd1, 0xea, 0x15, 0x82 }; - - auto has_type = [&](auto tag, std::initializer_list types, std::initializer_list v4_types) { - if (auto type = m_tag_table.get(tag); type.has_value()) { - auto type_matches = [&](auto wanted_type) { return type.value()->type() == wanted_type; }; - return any_of(types, type_matches) || (is_v4() && any_of(v4_types, type_matches)); - } - return true; - }; - - // ICC v4, 9.2.1 AToB0Tag - // "Permitted tag types: lut8Type or lut16Type or lutAToBType" - // ICC v2, 6.4.1 AToB0Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(AToB0Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutAToBTagData::Type })) - return Error::from_string_literal("ICC::Profile: AToB0Tag has unexpected type"); - - // ICC v4, 9.2.2 AToB1Tag - // "Permitted tag types: lut8Type or lut16Type or lutAToBType" - // ICC v2, 6.4.2 AToB1Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(AToB1Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutAToBTagData::Type })) - return Error::from_string_literal("ICC::Profile: AToB1Tag has unexpected type"); - - // ICC v4, 9.2.3 AToB2Tag - // "Permitted tag types: lut8Type or lut16Type or lutAToBType" - // ICC v2, 6.4.3 AToB2Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(AToB2Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutAToBTagData::Type })) - return Error::from_string_literal("ICC::Profile: AToB2Tag has unexpected type"); - - // ICC v4, 9.2.4 blueMatrixColumnTag - // "Permitted tag types: XYZType - // This tag contains the third column in the matrix used in matrix/TRC transforms." - // (Called blueColorantTag in the v2 spec, otherwise identical there.) - if (auto type = m_tag_table.get(blueMatrixColumnTag); type.has_value()) { - if (type.value()->type() != XYZTagData::Type) - return Error::from_string_literal("ICC::Profile: blueMatrixColumnTag has unexpected type"); - if (static_cast(*type.value()).xyzs().size() != 1) - return Error::from_string_literal("ICC::Profile: blueMatrixColumnTag has unexpected size"); - } - - // ICC v4, 9.2.5 blueTRCTag - // "Permitted tag types: curveType or parametricCurveType" - // ICC v2, 6.4.5 blueTRCTag - // "Tag Type: curveType" - if (!has_type(blueTRCTag, { CurveTagData::Type }, { ParametricCurveTagData::Type })) - return Error::from_string_literal("ICC::Profile: blueTRCTag has unexpected type"); - - // ICC v4, 9.2.6 BToA0Tag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.6 BToA0Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(BToA0Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: BToA0Tag has unexpected type"); - - // ICC v4, 9.2.7 BToA1Tag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.7 BToA1Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(BToA1Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: BToA1Tag has unexpected type"); - - // ICC v4, 9.2.8 BToA2Tag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.8 BToA2Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(BToA2Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: BToA2Tag has unexpected type"); - - // ICC v4, 9.2.9 BToD0Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.10 BToD1Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.11 BToD2Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.12 BToD3Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.13 calibrationDateTimeTag - // "Permitted tag types: dateTimeType" - // FIXME - - // ICC v4, 9.2.14 charTargetTag - // "Permitted tag types: textType" - if (!has_type(charTargetTag, { TextTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: charTargetTag has unexpected type"); - - // ICC v4, 9.2.15 chromaticAdaptationTag - // "Permitted tag types: s15Fixed16ArrayType [...] - // Such a 3 x 3 chromatic adaptation matrix is organized as a 9-element array" - if (auto type = m_tag_table.get(chromaticAdaptationTag); type.has_value()) { - if (type.value()->type() != S15Fixed16ArrayTagData::Type) - return Error::from_string_literal("ICC::Profile: chromaticAdaptationTag has unexpected type"); - if (static_cast(*type.value()).values().size() != 9) - return Error::from_string_literal("ICC::Profile: chromaticAdaptationTag has unexpected size"); - } - - // ICC v4, 9.2.16 chromaticityTag - // "Permitted tag types: chromaticityType" - if (!has_type(chromaticityTag, { ChromaticityTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: ChromaticityTagData has unexpected type"); - - // ICC v4, 9.2.17 cicpTag - // "Permitted tag types: cicpType" - if (auto type = m_tag_table.get(cicpTag); type.has_value()) { - if (type.value()->type() != CicpTagData::Type) - return Error::from_string_literal("ICC::Profile: cicpTag has unexpected type"); - - // "The colour encoding specified by the CICP tag content shall be equivalent to the data colour space encoding - // represented by this ICC profile. - // NOTE The ICC colour transform cannot match every possible rendering of a CICP colour encoding." - // FIXME: Figure out what that means and check for it. - - // "This tag may be present when the data colour space in the profile header is RGB, YCbCr, or XYZ, and the - // profile class in the profile header is Input or Display. The tag shall not be present for other data colour spaces - // or profile classes indicated in the profile header." - bool is_color_space_allowed = data_color_space() == ColorSpace::RGB || data_color_space() == ColorSpace::YCbCr || data_color_space() == ColorSpace::nCIEXYZ; - bool is_profile_class_allowed = device_class() == DeviceClass::InputDevice || device_class() == DeviceClass::DisplayDevice; - bool cicp_is_allowed = is_color_space_allowed && is_profile_class_allowed; - if (!cicp_is_allowed) - return Error::from_string_literal("ICC::Profile: cicpTag present but not allowed"); - } - - // ICC v4, 9.2.18 colorantOrderTag - // "Permitted tag types: colorantOrderType" - // FIXME - - // ICC v4, 9.2.19 colorantTableTag - // "Permitted tag types: colorantTableType" - // FIXME - - // ICC v4, 9.2.20 colorantTableOutTag - // "Permitted tag types: colorantTableType" - // FIXME - - // ICC v4, 9.2.21 colorimetricIntentImageStateTag - // "Permitted tag types: signatureType" - if (!has_type(colorimetricIntentImageStateTag, { SignatureTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: colorimetricIntentImageStateTag has unexpected type"); - - // ICC v4, 9.2.22 copyrightTag - // "Permitted tag types: multiLocalizedUnicodeType" - // ICC v2, 6.4.13 copyrightTag - // "Tag Type: textType" - if (auto type = m_tag_table.get(copyrightTag); type.has_value()) { - // The v4 spec requires multiLocalizedUnicodeType for this, but I'm aware of a single file - // that still uses the v2 'text' type here: /System/Library/ColorSync/Profiles/ITU-2020.icc on macOS 13.1. - // https://openradar.appspot.com/radar?id=5529765549178880 - bool has_v2_cprt_type_in_v4_file_quirk = id() == apple_itu_2020_id || id() == apple_p3_2015_id || id() == apple_p3_2017_id; - if (is_v4() && type.value()->type() != MultiLocalizedUnicodeTagData::Type && (!has_v2_cprt_type_in_v4_file_quirk || type.value()->type() != TextTagData::Type)) - return Error::from_string_literal("ICC::Profile: copyrightTag has unexpected v4 type"); - if (is_v2() && type.value()->type() != TextTagData::Type) - return Error::from_string_literal("ICC::Profile: copyrightTag has unexpected v2 type"); - } - - // ICC v4, 9.2.23 deviceMfgDescTag - // "Permitted tag types: multiLocalizedUnicodeType" - // ICC v2, 6.4.15 deviceMfgDescTag - // "Tag Type: textDescriptionType" - if (auto type = m_tag_table.get(deviceMfgDescTag); type.has_value()) { - if (is_v4() && type.value()->type() != MultiLocalizedUnicodeTagData::Type) - return Error::from_string_literal("ICC::Profile: deviceMfgDescTag has unexpected v4 type"); - if (is_v2() && type.value()->type() != TextDescriptionTagData::Type) - return Error::from_string_literal("ICC::Profile: deviceMfgDescTag has unexpected v2 type"); - } - - // ICC v4, 9.2.24 deviceModelDescTag - // "Permitted tag types: multiLocalizedUnicodeType" - // ICC v2, 6.4.16 deviceModelDescTag - // "Tag Type: textDescriptionType" - if (auto type = m_tag_table.get(deviceModelDescTag); type.has_value()) { - if (is_v4() && type.value()->type() != MultiLocalizedUnicodeTagData::Type) - return Error::from_string_literal("ICC::Profile: deviceModelDescTag has unexpected v4 type"); - if (is_v2() && type.value()->type() != TextDescriptionTagData::Type) - return Error::from_string_literal("ICC::Profile: deviceModelDescTag has unexpected v2 type"); - } - - // ICC v4, 9.2.25 DToB0Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.26 DToB1Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.27 DToB2Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.28 DToB3Tag - // "Permitted tag types: multiProcessElementsType" - // FIXME - - // ICC v4, 9.2.29 gamutTag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.18 gamutTag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(gamutTag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: gamutTag has unexpected type"); - - // ICC v4, 9.2.30 grayTRCTag - // "Permitted tag types: curveType or parametricCurveType" - // ICC v2, 6.4.19 grayTRCTag - // "Tag Type: curveType" - if (!has_type(grayTRCTag, { CurveTagData::Type }, { ParametricCurveTagData::Type })) - return Error::from_string_literal("ICC::Profile: grayTRCTag has unexpected type"); - - // ICC v4, 9.2.31 greenMatrixColumnTag - // "Permitted tag types: XYZType - // This tag contains the second column in the matrix, which is used in matrix/TRC transforms." - // (Called greenColorantTag in the v2 spec, otherwise identical there.) - if (auto type = m_tag_table.get(greenMatrixColumnTag); type.has_value()) { - if (type.value()->type() != XYZTagData::Type) - return Error::from_string_literal("ICC::Profile: greenMatrixColumnTag has unexpected type"); - if (static_cast(*type.value()).xyzs().size() != 1) - return Error::from_string_literal("ICC::Profile: greenMatrixColumnTag has unexpected size"); - } - - // ICC v4, 9.2.32 greenTRCTag - // "Permitted tag types: curveType or parametricCurveType" - // ICC v2, 6.4.21 greenTRCTag - // "Tag Type: curveType" - if (!has_type(greenTRCTag, { CurveTagData::Type }, { ParametricCurveTagData::Type })) - return Error::from_string_literal("ICC::Profile: greenTRCTag has unexpected type"); - - // ICC v4, 9.2.33 luminanceTag - // "Permitted tag types: XYZType" - // This tag contains the absolute luminance of emissive devices in candelas per square metre as described by the - // Y channel. - // NOTE The X and Z values are set to zero." - // ICC v2, 6.4.22 luminanceTag - // "Absolute luminance of emissive devices in candelas per square meter as described by the Y channel. The - // X and Z channels are ignored in all cases." - if (auto type = m_tag_table.get(luminanceTag); type.has_value()) { - if (type.value()->type() != XYZTagData::Type) - return Error::from_string_literal("ICC::Profile: luminanceTag has unexpected type"); - auto& xyz_type = static_cast(*type.value()); - if (xyz_type.xyzs().size() != 1) - return Error::from_string_literal("ICC::Profile: luminanceTag has unexpected size"); - if (is_v4() && xyz_type.xyzs()[0].X != 0) - return Error::from_string_literal("ICC::Profile: luminanceTag.x unexpectedly not 0"); - if (is_v4() && xyz_type.xyzs()[0].Z != 0) - return Error::from_string_literal("ICC::Profile: luminanceTag.z unexpectedly not 0"); - } - - // ICC v4, 9.2.34 measurementTag - // "Permitted tag types: measurementType" - if (!has_type(measurementTag, { MeasurementTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: measurementTag has unexpected type"); - - // ICC v4, 9.2.35 metadataTag - // "Permitted tag types: dictType" - // FIXME - - // ICC v4, 9.2.36 mediaWhitePointTag - // "Permitted tag types: XYZType - // This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically adapted - // nCIEXYZ tristimulus values of the media white point. When the measurement data used to create the profile - // were specified relative to an adopted white with a chromaticity different from that of the PCS adopted white, the - // media white point nCIEXYZ values shall be adapted to be relative to the PCS adopted white chromaticity using - // the chromaticAdaptationTag matrix, before recording in the tag. For capture devices, the media white point is - // the encoding maximum white for the capture encoding. For displays, the values specified shall be those of the - // PCS illuminant as defined in 7.2.16. - // See Clause 6 and Annex A for a more complete description of the use of the media white point." - // ICC v2, 6.4.25 mediaWhitePointTag - // "This tag specifies the media white point and is used for generating ICC-absolute colorimetric intent. See - // Annex A for a more complete description of its use." - if (auto type = m_tag_table.get(mediaWhitePointTag); type.has_value()) { - if (type.value()->type() != XYZTagData::Type) - return Error::from_string_literal("ICC::Profile: mediaWhitePointTag has unexpected type"); - auto& xyz_type = static_cast(*type.value()); - if (xyz_type.xyzs().size() != 1) - return Error::from_string_literal("ICC::Profile: mediaWhitePointTag has unexpected size"); - - // V4 requires "For displays, the values specified shall be those of the PCS illuminant". - // But in practice that's not always true. For example, on macOS 13.1, '/System/Library/ColorSync/Profiles/DCI(P3) RGB.icc' - // has these values in the header: 0000F6D6 00010000 0000D32D - // but these values in the tag: 0000F6D5 00010000 0000D32C - // These are close, but not equal. - // FIXME: File bug for these, and add id-based quirk instead. - // if (is_v4() && device_class() == DeviceClass::DisplayDevice && xyz_type.xyzs()[0] != pcs_illuminant()) - // return Error::from_string_literal("ICC::Profile: mediaWhitePointTag for displays should be equal to PCS illuminant"); - } - - // ICC v4, 9.2.37 namedColor2Tag - // "Permitted tag types: namedColor2Type" - if (auto type = m_tag_table.get(namedColor2Tag); type.has_value()) { - if (type.value()->type() != NamedColor2TagData::Type) - return Error::from_string_literal("ICC::Profile: namedColor2Tag has unexpected type"); - // ICC v4, 10.17 namedColor2Type - // "The device representation corresponds to the header’s “data colour space” field. - // This representation should be consistent with the “number of device coordinates” field in the namedColor2Type. - // If this field is 0, device coordinates are not provided." - if (auto number_of_device_coordinates = static_cast(*type.value()).number_of_device_coordinates(); - number_of_device_coordinates != 0 && number_of_device_coordinates != number_of_components_in_color_space(data_color_space())) { - return Error::from_string_literal("ICC::Profile: namedColor2Tag number of device coordinates inconsistent with data color space"); - } - } - - // ICC v4, 9.2.38 outputResponseTag - // "Permitted tag types: responseCurveSet16Type" - // FIXME - - // ICC v4, 9.2.39 perceptualRenderingIntentGamutTag - // "Permitted tag types: signatureType" - if (!has_type(perceptualRenderingIntentGamutTag, { SignatureTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: perceptualRenderingIntentGamutTag has unexpected type"); - - // ICC v4, 9.2.40 preview0Tag - // "Permitted tag types: lut8Type or lut16Type or lutAToBType or lutBToAType" - // ICC v2, 6.4.29 preview0Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(preview0Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type, LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: preview0Tag has unexpected type"); - - // ICC v4, 9.2.41 preview1Tag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.30 preview1Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(preview1Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: preview1Tag has unexpected type"); - - // ICC v4, 9.2.42 preview2Tag - // "Permitted tag types: lut8Type or lut16Type or lutBToAType" - // ICC v2, 6.4.31 preview2Tag - // "Tag Type: lut8Type or lut16Type" - if (!has_type(preview2Tag, { Lut8TagData::Type, Lut16TagData::Type }, { LutBToATagData::Type })) - return Error::from_string_literal("ICC::Profile: preview2Tag has unexpected type"); - - // ICC v4, 9.2.43 profileDescriptionTag - // "Permitted tag types: multiLocalizedUnicodeType" - // ICC v2, 6.4.32 profileDescriptionTag - // "Tag Type: textDescriptionType" - if (auto type = m_tag_table.get(profileDescriptionTag); type.has_value()) { - // The v4 spec requires multiLocalizedUnicodeType for this, but I'm aware of a single file - // that still uses the v2 'desc' type here: /System/Library/ColorSync/Profiles/ITU-2020.icc on macOS 13.1. - // https://openradar.appspot.com/radar?id=5529765549178880 - bool has_v2_desc_type_in_v4_file_quirk = id() == apple_itu_2020_id || id() == apple_p3_2015_id || id() == apple_p3_2017_id; - if (is_v4() && type.value()->type() != MultiLocalizedUnicodeTagData::Type && (!has_v2_desc_type_in_v4_file_quirk || type.value()->type() != TextDescriptionTagData::Type)) - return Error::from_string_literal("ICC::Profile: profileDescriptionTag has unexpected v4 type"); - if (is_v2() && type.value()->type() != TextDescriptionTagData::Type) - return Error::from_string_literal("ICC::Profile: profileDescriptionTag has unexpected v2 type"); - } - - // ICC v4, 9.2.44 profileSequenceDescTag - // "Permitted tag types: profileSequenceDescType" - // FIXME - - // ICC v4, 9.2.45 profileSequenceIdentifierTag - // "Permitted tag types: profileSequenceIdentifierType" - // FIXME - - // ICC v4, 9.2.46 redMatrixColumnTag - // "Permitted tag types: XYZType - // This tag contains the first column in the matrix, which is used in matrix/TRC transforms." - // (Called redColorantTag in the v2 spec, otherwise identical there.) - if (auto type = m_tag_table.get(redMatrixColumnTag); type.has_value()) { - if (type.value()->type() != XYZTagData::Type) - return Error::from_string_literal("ICC::Profile: redMatrixColumnTag has unexpected type"); - if (static_cast(*type.value()).xyzs().size() != 1) - return Error::from_string_literal("ICC::Profile: redMatrixColumnTag has unexpected size"); - } - - // ICC v4, 9.2.47 redTRCTag - // "Permitted tag types: curveType or parametricCurveType" - // ICC v2, 6.4.41 redTRCTag - // "Tag Type: curveType" - if (!has_type(redTRCTag, { CurveTagData::Type }, { ParametricCurveTagData::Type })) - return Error::from_string_literal("ICC::Profile: redTRCTag has unexpected type"); - - // ICC v4, 9.2.48 saturationRenderingIntentGamutTag - // "Permitted tag types: signatureType" - if (!has_type(saturationRenderingIntentGamutTag, { SignatureTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: saturationRenderingIntentGamutTag has unexpected type"); - - // ICC v4, 9.2.49 technologyTag - // "Permitted tag types: signatureType" - if (!has_type(technologyTag, { SignatureTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: technologyTag has unexpected type"); - - // ICC v4, 9.2.50 viewingCondDescTag - // "Permitted tag types: multiLocalizedUnicodeType" - // ICC v2, 6.4.46 viewingCondDescTag - // "Tag Type: textDescriptionType" - if (auto type = m_tag_table.get(viewingCondDescTag); type.has_value()) { - if (is_v4() && type.value()->type() != MultiLocalizedUnicodeTagData::Type) - return Error::from_string_literal("ICC::Profile: viewingCondDescTag has unexpected v4 type"); - if (is_v2() && type.value()->type() != TextDescriptionTagData::Type) - return Error::from_string_literal("ICC::Profile: viewingCondDescTag has unexpected v2 type"); - } - - // ICC v4, 9.2.51 viewingConditionsTag - // "Permitted tag types: viewingConditionsType" - if (!has_type(viewingConditionsTag, { ViewingConditionsTagData::Type }, {})) - return Error::from_string_literal("ICC::Profile: viewingConditionsTag has unexpected type"); - - // FIXME: Add validation for v2-only tags: - // - ICC v2, 6.4.14 crdInfoTag - // "Tag Type: crdInfoType" - // - ICC v2, 6.4.17 deviceSettingsTag - // "Tag Type: deviceSettingsType" - // - ICC v2, 6.4.24 mediaBlackPointTag - // "Tag Type: XYZType" - // - ICC v2, 6.4.26 namedColorTag - // "Tag Type: namedColorType" - // - ICC v2, 6.4.34 ps2CRD0Tag - // "Tag Type: dataType" - // - ICC v2, 6.4.35 ps2CRD1Tag - // "Tag Type: dataType" - // - ICC v2, 6.4.36 ps2CRD2Tag - // "Tag Type: dataType" - // - ICC v2, 6.4.37 ps2CRD3Tag - // "Tag Type: dataType" - // - ICC v2, 6.4.38 ps2CSATag - // "Tag Type: dataType" - // - ICC v2, 6.4.39 ps2RenderingIntentTag - // "Tag Type: dataType" - // - ICC v2, 6.4.42 screeningDescTag - // "Tag Type: textDescriptionType" - // - ICC v2, 6.4.43 screeningTag - // "Tag Type: screeningType" - // - ICC v2, 6.4.45 ucrbgTag - // "Tag Type: ucrbgType" - // https://www.color.org/v2profiles.xalter says about these tags: - // "it is also recommended that optional tags in the v2 specification that have subsequently become - // obsolete are not included in future profiles made to the v2 specification." - - return {}; -} - -ErrorOr> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes) -{ - auto header = TRY(read_header(bytes)); - bytes = bytes.trim(header.on_disk_size); - auto tag_table = TRY(read_tag_table(bytes)); - - return create(header, move(tag_table)); -} - -ErrorOr> Profile::create(ProfileHeader const& header, OrderedHashMap> tag_table) -{ - auto profile = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) Profile(header, move(tag_table)))); - - TRY(profile->check_required_tags()); - TRY(profile->check_tag_types()); - - return profile; -} - -Crypto::Hash::MD5::DigestType Profile::compute_id(ReadonlyBytes bytes) -{ - // ICC v4, 7.2.18 Profile ID field - // "The Profile ID shall be calculated using the MD5 fingerprinting method as defined in Internet RFC 1321. - // The entire profile, whose length is given by the size field in the header, with the - // profile flags field (bytes 44 to 47, see 7.2.11), - // rendering intent field (bytes 64 to 67, see 7.2.15), - // and profile ID field (bytes 84 to 99) - // in the profile header temporarily set to zeros (00h), - // shall be used to calculate the ID." - u8 const zero[16] = {}; - Crypto::Hash::MD5 md5; - md5.update(bytes.slice(0, 44)); - md5.update(ReadonlyBytes { zero, 4 }); // profile flags field - md5.update(bytes.slice(48, 64 - 48)); - md5.update(ReadonlyBytes { zero, 4 }); // rendering intent field - md5.update(bytes.slice(68, 84 - 68)); - md5.update(ReadonlyBytes { zero, 16 }); // profile ID field - md5.update(bytes.slice(100)); - return md5.digest(); -} - -static TagSignature forward_transform_tag_for_rendering_intent(RenderingIntent rendering_intent) -{ - // ICCv4, Table 25 — Profile type/profile tag and defined rendering intents - // This function assumes a profile class of InputDevice, DisplayDevice, OutputDevice, or ColorSpace. - switch (rendering_intent) { - case RenderingIntent::Perceptual: - return AToB0Tag; - case RenderingIntent::MediaRelativeColorimetric: - case RenderingIntent::ICCAbsoluteColorimetric: - return AToB1Tag; - case RenderingIntent::Saturation: - return AToB2Tag; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes color) const -{ - // Assumes a "normal" device_class() (i.e. not DeviceLink). - VERIFY(number_of_components_in_color_space(connection_space()) == 3); - - if (m_to_pcs_clut_cache.has_value() && m_to_pcs_clut_cache->key == color) - return m_to_pcs_clut_cache->value; - - FloatVector3 result; - - switch (tag_data.type()) { - case Lut16TagData::Type: { - auto const& a_to_b = static_cast(tag_data); - result = TRY(a_to_b.evaluate(data_color_space(), connection_space(), color)); - break; - } - case Lut8TagData::Type: { - auto const& a_to_b = static_cast(tag_data); - result = TRY(a_to_b.evaluate(data_color_space(), connection_space(), color)); - break; - } - case LutAToBTagData::Type: { - auto const& a_to_b = static_cast(tag_data); - if (a_to_b.number_of_input_channels() != number_of_components_in_color_space(data_color_space())) - return Error::from_string_literal("ICC::Profile::to_pcs_a_to_b: mAB input channel count does not match color space size"); - - if (a_to_b.number_of_output_channels() != number_of_components_in_color_space(connection_space())) - return Error::from_string_literal("ICC::Profile::to_pcs_a_to_b: mAB output channel count does not match profile connection space size"); - - result = TRY(a_to_b.evaluate(connection_space(), color)); - break; - } - default: - VERIFY_NOT_REACHED(); - } - - if (!m_to_pcs_clut_cache.has_value()) - m_to_pcs_clut_cache = OneElementCLUTCache {}; - m_to_pcs_clut_cache->key = Vector(color); - m_to_pcs_clut_cache->value = result; - - return result; -} - -ErrorOr Profile::to_pcs(ReadonlyBytes color) const -{ - if (color.size() != number_of_components_in_color_space(data_color_space())) - return Error::from_string_literal("ICC::Profile: input color doesn't match color space size"); - - auto get_tag = [&](auto tag) { return m_tag_table.get(tag); }; - - switch (device_class()) { - case DeviceClass::InputDevice: - case DeviceClass::DisplayDevice: - case DeviceClass::OutputDevice: - case DeviceClass::ColorSpace: { - // ICC v4, 8.10 Precedence order of tag usage - // "There are several methods of colour transformation that can function within a single CMM. If data for more than - // one method are included in the same profile, the following selection algorithm shall be used by the software - // implementation." - // ICC v4, 8.10.2 Input, display, output, or colour space profile types - // "a) Use the BToD0Tag, BToD1Tag, BToD2Tag, BToD3Tag, DToB0Tag, DToB1Tag, DToB2Tag, or - // DToB3Tag designated for the rendering intent if the tag is present, except where this tag is not needed or - // supported by the CMM (if a particular processing element within the tag is not supported the tag is not - // supported)." - // FIXME: Implement multiProcessElementsType one day. - - // "b) Use the BToA0Tag, BToA1Tag, BToA2Tag, AToB0Tag, AToB1Tag, or AToB2Tag designated for the - // rendering intent if present, when the tag in a) is not used." - if (m_cached_has_any_a_to_b_tag) - if (auto tag = get_tag(forward_transform_tag_for_rendering_intent(rendering_intent())); tag.has_value()) - return to_pcs_a_to_b(*tag.value(), color); - - // "c) Use the BToA0Tag or AToB0Tag if present, when the tags in a) and b) are not used." - // AToB0Tag is for the conversion _to_ PCS (BToA0Tag is for conversion _from_ PCS, so not needed in this function). - if (m_cached_has_a_to_b0_tag) - if (auto tag = get_tag(AToB0Tag); tag.has_value()) - return to_pcs_a_to_b(*tag.value(), color); - - // "d) Use TRCs (redTRCTag, greenTRCTag, blueTRCTag, or grayTRCTag) and colorants - // (redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag) when tags in a), b), and c) are not - // used." - auto evaluate_curve = [this](TagSignature curve_tag, float f) { - auto const& trc = *m_tag_table.get(curve_tag).value(); - VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); - if (trc.type() == CurveTagData::Type) - return static_cast(trc).evaluate(f); - return static_cast(trc).evaluate(f); - }; - - if (data_color_space() == ColorSpace::Gray) { - VERIFY(color.size() == 1); // True because of color.size() check further up. - - // ICC v4, F.2 grayTRCTag - // "connection = grayTRC[device]" - float gray = evaluate_curve(grayTRCTag, color[0] / 255.f); - FloatVector3 white { pcs_illuminant().X, pcs_illuminant().Y, pcs_illuminant().Z }; - return white * gray; - } - - // FIXME: Per ICC v4, A.1 General, this should also handle HLS, HSV, YCbCr. - if (data_color_space() == ColorSpace::RGB) { - if (!m_cached_has_all_rgb_matrix_tags) - return Error::from_string_literal("ICC::Profile::to_pcs: RGB color space but neither LUT-based nor matrix-based tags present"); - VERIFY(color.size() == 3); // True because of color.size() check further up. - - // ICC v4, F.3 Three-component matrix-based profiles - // "linear_r = redTRC[device_r] - // linear_g = greenTRC[device_g] - // linear_b = blueTRC[device_b] - // [connection_X] = [redMatrixColumn_X greenMatrixColumn_X blueMatrixColumn_X] [ linear_r ] - // [connection_Y] = [redMatrixColumn_Y greenMatrixColumn_Y blueMatrixColumn_Y] * [ linear_g ] - // [connection_Z] = [redMatrixColumn_Z greenMatrixColumn_Z blueMatrixColumn_Z] [ linear_b ]" - FloatVector3 linear_rgb { - evaluate_curve(redTRCTag, color[0] / 255.f), - evaluate_curve(greenTRCTag, color[1] / 255.f), - evaluate_curve(blueTRCTag, color[2] / 255.f), - }; - - return rgb_to_xyz_matrix() * linear_rgb; - } - - return Error::from_string_literal("ICC::Profile::to_pcs: What happened?!"); - } - - case DeviceClass::DeviceLink: - case DeviceClass::Abstract: - // ICC v4, 8.10.3 DeviceLink or Abstract profile types - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: conversion for DeviceLink and Abstract not implemented"); - - case DeviceClass::NamedColor: - return Error::from_string_literal("ICC::Profile::to_pcs: to_pcs with NamedColor profile does not make sense"); - } - VERIFY_NOT_REACHED(); -} - -static FloatVector3 lab_from_xyz(FloatVector3 xyz, XYZ white_point) -{ - // 6.3.2.2 Translation between media-relative colorimetric data and ICC-absolute colorimetric data - // 6.3.2.3 Computation of PCSLAB - // 6.3.4 Colour space encodings for the PCS - // A.3 PCS encodings - - auto f = [](float x) { - if (x > powf(6.f / 29.f, 3)) - return cbrtf(x); - return x / (3 * powf(6.f / 29.f, 2)) + 4.f / 29.f; - }; - - // "X/Xn is replaced by Xr/Xi (or Xa/Xmw)" - - // 6.3.2.2 Translation between media-relative colorimetric data and ICC-absolute colorimetric data - // "The translation from ICC-absolute colorimetric data to media-relative colorimetry data is given by Equations - // Xr = (Xi/Xmw) * Xa - // where - // Xr media-relative colorimetric data (i.e. PCSXYZ); - // Xa ICC-absolute colorimetric data (i.e. nCIEXYZ); - // Xmw nCIEXYZ values of the media white point as specified in the mediaWhitePointTag; - // Xi PCSXYZ values of the PCS white point defined in 6.3.4.3." - // 6.3.4.3 PCS encodings for white and black - // "Table 14 — Encodings of PCS white point: X 0,9642 Y 1,0000 Z 0,8249" - // That's identical to the values in 7.2.16 PCS illuminant field (Bytes 68 to 79). - // 9.2.36 mediaWhitePointTag - // "For displays, the values specified shall be those of the PCS illuminant as defined in 7.2.16." - // ...so for displays, this is all equivalent I think? It's maybe different for OutputDevice profiles? - - float Xn = white_point.X; - float Yn = white_point.Y; - float Zn = white_point.Z; - - float x = xyz[0] / Xn; - float y = xyz[1] / Yn; - float z = xyz[2] / Zn; - - float L = 116 * f(y) - 16; - float a = 500 * (f(x) - f(y)); - float b = 200 * (f(y) - f(z)); - - return { L, a, b }; -} - -static FloatVector3 xyz_from_lab(FloatVector3 lab, XYZ white_point) -{ - // Inverse of lab_from_xyz(). - auto L_star = lab[0]; - auto a_star = lab[1]; - auto b_star = lab[2]; - - auto L = (L_star + 16) / 116 + a_star / 500; // f(x) - auto M = (L_star + 16) / 116; // f(y) - auto N = (L_star + 16) / 116 - b_star / 200; // f(z) - - // Inverse of f in lab_from_xyz(). - auto g = [](float x) { - if (x >= 6.0f / 29.0f) - return powf(x, 3); - return (x - 4.0f / 29.0f) * (3 * powf(6.f / 29.f, 2)); - }; - - return { white_point.X * g(L), white_point.Y * g(M), white_point.Z * g(N) }; -} - -static TagSignature backward_transform_tag_for_rendering_intent(RenderingIntent rendering_intent) -{ - // ICCv4, Table 25 — Profile type/profile tag and defined rendering intents - // This function assumes a profile class of InputDevice, DisplayDevice, OutputDevice, or ColorSpace. - switch (rendering_intent) { - case RenderingIntent::Perceptual: - return BToA0Tag; - case RenderingIntent::MediaRelativeColorimetric: - case RenderingIntent::ICCAbsoluteColorimetric: - return BToA1Tag; - case RenderingIntent::Saturation: - return BToA2Tag; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr Profile::from_pcs_b_to_a(TagData const& tag_data, FloatVector3 const& pcs, Bytes out_bytes) const -{ - switch (tag_data.type()) { - case Lut16TagData::Type: - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: BToA*Tag handling for mft2 tags not yet implemented"); - case Lut8TagData::Type: - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: BToA*Tag handling for mft1 tags not yet implemented"); - case LutBToATagData::Type: { - auto const& b_to_a = static_cast(tag_data); - if (b_to_a.number_of_input_channels() != number_of_components_in_color_space(connection_space())) - return Error::from_string_literal("ICC::Profile::from_pcs_b_to_a: mBA input channel count does not match color space size"); - - if (b_to_a.number_of_output_channels() != number_of_components_in_color_space(data_color_space())) - return Error::from_string_literal("ICC::Profile::from_pcs_b_to_a: mBA output channel count does not match profile connection space size"); - - return b_to_a.evaluate(connection_space(), pcs, out_bytes); - } - } - VERIFY_NOT_REACHED(); -} - -ErrorOr Profile::from_pcs(Profile const& source_profile, FloatVector3 pcs, Bytes color) const -{ - if (source_profile.connection_space() != connection_space()) { - if (source_profile.connection_space() == ColorSpace::PCSLAB) { - VERIFY(connection_space() == ColorSpace::PCSXYZ); - pcs = xyz_from_lab(pcs, source_profile.pcs_illuminant()); - } else { - VERIFY(source_profile.connection_space() == ColorSpace::PCSXYZ); - VERIFY(connection_space() == ColorSpace::PCSLAB); - pcs = lab_from_xyz(pcs, pcs_illuminant()); - } - } - - // See `to_pcs()` for spec links. - // This function is very similar, but uses BToAn instead of AToBn for LUT profiles, - // and an inverse transform for matrix profiles. - if (color.size() != number_of_components_in_color_space(data_color_space())) - return Error::from_string_literal("ICC::Profile: output color doesn't match color space size"); - - auto get_tag = [&](auto tag) { return m_tag_table.get(tag); }; - - switch (device_class()) { - case DeviceClass::InputDevice: - case DeviceClass::DisplayDevice: - case DeviceClass::OutputDevice: - case DeviceClass::ColorSpace: { - // FIXME: Implement multiProcessElementsType one day. - - if (m_cached_has_any_b_to_a_tag) - if (auto tag = get_tag(backward_transform_tag_for_rendering_intent(rendering_intent())); tag.has_value()) - return from_pcs_b_to_a(*tag.value(), pcs, color); - - if (m_cached_has_b_to_a0_tag) - if (auto tag = get_tag(BToA0Tag); tag.has_value()) - return from_pcs_b_to_a(*tag.value(), pcs, color); - - if (data_color_space() == ColorSpace::Gray) { - // FIXME - return Error::from_string_literal("ICC::Profile::from_pcs: Gray handling not yet implemented"); - } - - // FIXME: Per ICC v4, A.1 General, this should also handle HLS, HSV, YCbCr. - if (data_color_space() == ColorSpace::RGB) { - if (!m_cached_has_all_rgb_matrix_tags) - return Error::from_string_literal("ICC::Profile::from_pcs: RGB color space but neither LUT-based nor matrix-based tags present"); - VERIFY(color.size() == 3); // True because of color.size() check further up. - - // ICC v4, F.3 Three-component matrix-based profiles - // "The inverse model is given by the following equations: - // [linear_r] = [redMatrixColumn_X greenMatrixColumn_X blueMatrixColumn_X]^-1 [ connection_X ] - // [linear_g] = [redMatrixColumn_Y greenMatrixColumn_Y blueMatrixColumn_Y] * [ connection_Y ] - // [linear_b] = [redMatrixColumn_Z greenMatrixColumn_Z blueMatrixColumn_Z] [ connection_Z ] - // - // for linear_r < 0, device_r = redTRC^-1[0] (F.8) - // for 0 ≤ linear_r ≤ 1, device_r = redTRC^-1[linear_r] (F.9) - // for linear_r > 1, device_r = redTRC^-1[1] (F.10) - // - // for linear_g < 0, device_g = greenTRC^-1[0] (F.11) - // for 0 ≤ linear_g ≤ 1, device_g = greenTRC^-1[linear_g] (F.12) - // for linear_g > 1, device_g = greenTRC^-1[1] (F.13) - // - // for linear_b < 0, device_b = blueTRC^-1[0] (F.14) - // for 0 ≤ linear_b ≤ 1, device_b = blueTRC^-1[linear_b] (F.15) - // for linear_b > 1, device_b = blueTRC^-1[1] (F.16) - // - // where redTRC^-1, greenTRC^-1, and blueTRC^-1 indicate the inverse functions of the redTRC greenTRC and - // blueTRC functions respectively. - // If the redTRC, greenTRC, or blueTRC function is not invertible the behaviour of the corresponding redTRC^-1, - // greenTRC^-1, and blueTRC^-1 function is undefined. If a one-dimensional curve is constant, the curve cannot be - // inverted." - - // Convert from XYZ to linear rgb. - // FIXME: Inverting curves on every call to this function is very inefficient. - FloatVector3 linear_rgb = TRY(xyz_to_rgb_matrix()) * pcs; - - auto evaluate_curve_inverse = [this](TagSignature curve_tag, float f) { - auto const& trc = *m_tag_table.get(curve_tag).value(); - VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); - if (trc.type() == CurveTagData::Type) - return static_cast(trc).evaluate_inverse(f); - return static_cast(trc).evaluate_inverse(f); - }; - - // Convert from linear rgb to device rgb. - // See equations (F.8) - (F.16) above. - // FIXME: The spec says to do this, but it loses information. Color.js returns unclamped - // values instead (...but how do those make it through the TRC?) and has a separate - // clipping step. Maybe that's better? - // Also, maybe doing actual gamut mapping would look better? - // (For LUT profiles, I think the gamut mapping is baked into the BToA* data in the profile (?). - // But for matrix profiles, it'd have to be done in code.) - linear_rgb.clamp(0.f, 1.f); - float device_r = evaluate_curve_inverse(redTRCTag, linear_rgb[0]); - float device_g = evaluate_curve_inverse(greenTRCTag, linear_rgb[1]); - float device_b = evaluate_curve_inverse(blueTRCTag, linear_rgb[2]); - - color[0] = round(255 * device_r); - color[1] = round(255 * device_g); - color[2] = round(255 * device_b); - return {}; - } - - return Error::from_string_literal("ICC::Profile::from_pcs: What happened?!"); - } - - case DeviceClass::DeviceLink: - case DeviceClass::Abstract: - // ICC v4, 8.10.3 DeviceLink or Abstract profile types - // FIXME - return Error::from_string_literal("ICC::Profile::from_pcs: conversion for DeviceLink and Abstract not implemented"); - - case DeviceClass::NamedColor: - return Error::from_string_literal("ICC::Profile::from_pcs: from_pcs with NamedColor profile does not make sense"); - } - VERIFY_NOT_REACHED(); -} - -ErrorOr Profile::to_lab(ReadonlyBytes color) const -{ - auto pcs = TRY(to_pcs(color)); - if (connection_space() == ColorSpace::PCSLAB) - return CIELAB { pcs[0], pcs[1], pcs[2] }; - - if (connection_space() != ColorSpace::PCSXYZ) { - VERIFY(device_class() == DeviceClass::DeviceLink); - return Error::from_string_literal("ICC::Profile::to_lab: conversion for DeviceLink not implemented"); - } - - FloatVector3 lab = lab_from_xyz(pcs, pcs_illuminant()); - return CIELAB { lab[0], lab[1], lab[2] }; -} - -MatrixMatrixConversion::MatrixMatrixConversion(LutCurveType source_red_TRC, - LutCurveType source_green_TRC, - LutCurveType source_blue_TRC, - FloatMatrix3x3 matrix, - LutCurveType destination_red_TRC, - LutCurveType destination_green_TRC, - LutCurveType destination_blue_TRC) - : m_source_red_TRC(move(source_red_TRC)) - , m_source_green_TRC(move(source_green_TRC)) - , m_source_blue_TRC(move(source_blue_TRC)) - , m_matrix(matrix) - , m_destination_red_TRC(move(destination_red_TRC)) - , m_destination_green_TRC(move(destination_green_TRC)) - , m_destination_blue_TRC(move(destination_blue_TRC)) -{ - auto check = [](auto const& trc) { - VERIFY(trc->type() == CurveTagData::Type || trc->type() == ParametricCurveTagData::Type); - }; - check(m_source_red_TRC); - check(m_source_green_TRC); - check(m_source_blue_TRC); - check(m_destination_red_TRC); - check(m_destination_green_TRC); - check(m_destination_blue_TRC); -} - -Optional Profile::matrix_matrix_conversion(Profile const& source_profile) const -{ - auto has_normal_device_class = [](DeviceClass device) { - return device == DeviceClass::InputDevice - || device == DeviceClass::DisplayDevice - || device == DeviceClass::OutputDevice - || device == DeviceClass::ColorSpace; - }; - - bool is_matrix_matrix_conversion = has_normal_device_class(device_class()) - && has_normal_device_class(source_profile.device_class()) - && connection_space() == ColorSpace::PCSXYZ - && source_profile.connection_space() == ColorSpace::PCSXYZ - && data_color_space() == ColorSpace::RGB - && source_profile.data_color_space() == ColorSpace::RGB - && !m_cached_has_any_a_to_b_tag - && !source_profile.m_cached_has_any_a_to_b_tag - && m_cached_has_all_rgb_matrix_tags - && source_profile.m_cached_has_all_rgb_matrix_tags - && rgb_to_xyz_matrix().is_invertible(); - - if (!is_matrix_matrix_conversion) - return OptionalNone {}; - - LutCurveType sourceRedTRC = *source_profile.m_tag_table.get(redTRCTag).value(); - LutCurveType sourceGreenTRC = *source_profile.m_tag_table.get(greenTRCTag).value(); - LutCurveType sourceBlueTRC = *source_profile.m_tag_table.get(blueTRCTag).value(); - - FloatMatrix3x3 matrix = MUST(xyz_to_rgb_matrix()) * source_profile.rgb_to_xyz_matrix(); - - LutCurveType destinationRedTRC = *m_tag_table.get(redTRCTag).value(); - LutCurveType destinationGreenTRC = *m_tag_table.get(greenTRCTag).value(); - LutCurveType destinationBlueTRC = *m_tag_table.get(blueTRCTag).value(); - - return MatrixMatrixConversion(sourceRedTRC, sourceGreenTRC, sourceBlueTRC, matrix, destinationRedTRC, destinationGreenTRC, destinationBlueTRC); -} - -ErrorOr Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, MatrixMatrixConversion const& map) const -{ - for (auto& pixel : bitmap) { - FloatVector3 rgb { (float)Color::from_argb(pixel).red(), (float)Color::from_argb(pixel).green(), (float)Color::from_argb(pixel).blue() }; - auto out = map.map(rgb / 255.0f); - out.set_alpha(Color::from_argb(pixel).alpha()); - pixel = out.value(); - } - return {}; -} - -ErrorOr Profile::convert_image(Gfx::Bitmap& bitmap, Profile const& source_profile) const -{ - if (auto map = matrix_matrix_conversion(source_profile); map.has_value()) - return convert_image_matrix_matrix(bitmap, map.value()); - - for (auto& pixel : bitmap) { - u8 rgb[] = { Color::from_argb(pixel).red(), Color::from_argb(pixel).green(), Color::from_argb(pixel).blue() }; - auto pcs = TRY(source_profile.to_pcs(rgb)); - TRY(from_pcs(source_profile, pcs, rgb)); - pixel = Color(rgb[0], rgb[1], rgb[2], Color::from_argb(pixel).alpha()).value(); - } - - return {}; -} - -ErrorOr Profile::convert_cmyk_image(Bitmap& out, CMYKBitmap const& in, Profile const& source_profile) const -{ - if (out.size() != in.size()) - return Error::from_string_literal("ICC::Profile::convert_cmyk_image: out and in must have the same dimensions"); - - // Might fail if `out` has a scale_factor() != 1. - if (out.data_size() != in.data_size()) - return Error::from_string_literal("ICC::Profile::convert_cmyk_image: out and in must have the same buffer size"); - - static_assert(sizeof(ARGB32) == sizeof(CMYK)); - ARGB32* out_data = out.begin(); - CMYK const* in_data = const_cast(in).begin(); - - for (size_t i = 0; i < in.data_size() / sizeof(CMYK); ++i) { - u8 cmyk[] = { in_data[i].c, in_data[i].m, in_data[i].y, in_data[i].k }; - auto pcs = TRY(source_profile.to_pcs(cmyk)); - - u8 rgb[3]; - TRY(from_pcs(source_profile, pcs, rgb)); - out_data[i] = Color(rgb[0], rgb[1], rgb[2]).value(); - } - - return {}; -} - -XYZ const& Profile::red_matrix_column() const { return xyz_data(redMatrixColumnTag); } -XYZ const& Profile::green_matrix_column() const { return xyz_data(greenMatrixColumnTag); } -XYZ const& Profile::blue_matrix_column() const { return xyz_data(blueMatrixColumnTag); } - -Optional Profile::tag_string_data(TagSignature signature) const -{ - auto maybe_tag_data = tag_data(signature); - if (!maybe_tag_data.has_value()) - return {}; - auto& tag_data = maybe_tag_data.release_value(); - if (tag_data.type() == Gfx::ICC::MultiLocalizedUnicodeTagData::Type) { - auto& multi_localized_unicode = static_cast(tag_data); - // Try to find 'en-US', otherwise any 'en' language, otherwise the first record. - Optional en_string; - constexpr u16 language_en = ('e' << 8) + 'n'; - constexpr u16 country_us = ('U' << 8) + 'S'; - for (auto const& record : multi_localized_unicode.records()) { - if (record.iso_639_1_language_code == language_en) { - if (record.iso_3166_1_country_code == country_us) - return record.text; - en_string = record.text; - } - } - if (en_string.has_value()) - return en_string.value(); - if (!multi_localized_unicode.records().is_empty()) - return multi_localized_unicode.records().first().text; - return {}; - } - if (tag_data.type() == Gfx::ICC::TextDescriptionTagData::Type) { - auto& text_description = static_cast(tag_data); - return text_description.ascii_description(); - } - if (tag_data.type() == Gfx::ICC::TextTagData::Type) { - auto& text = static_cast(tag_data); - return text.text(); - } - return {}; -} - -ErrorOr Profile::xyz_to_rgb_matrix() const -{ - if (!m_cached_xyz_to_rgb_matrix.has_value()) { - FloatMatrix3x3 forward_matrix = rgb_to_xyz_matrix(); - if (!forward_matrix.is_invertible()) - return Error::from_string_literal("ICC::Profile::from_pcs: matrix not invertible"); - m_cached_xyz_to_rgb_matrix = forward_matrix.inverse(); - } - return m_cached_xyz_to_rgb_matrix.value(); -} - -FloatMatrix3x3 Profile::rgb_to_xyz_matrix() const -{ - auto const& red_matrix_column = this->red_matrix_column(); - auto const& green_matrix_column = this->green_matrix_column(); - auto const& blue_matrix_column = this->blue_matrix_column(); - - return FloatMatrix3x3 { - red_matrix_column.X, green_matrix_column.X, blue_matrix_column.X, - red_matrix_column.Y, green_matrix_column.Y, blue_matrix_column.Y, - red_matrix_column.Z, green_matrix_column.Z, blue_matrix_column.Z - }; -} - -} diff --git a/Libraries/LibGfx/ICC/Profile.h b/Libraries/LibGfx/ICC/Profile.h deleted file mode 100644 index a96981741ab75..0000000000000 --- a/Libraries/LibGfx/ICC/Profile.h +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2022-2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Gfx::ICC { - -URL::URL device_manufacturer_url(DeviceManufacturer); -URL::URL device_model_url(DeviceModel); - -// ICC v4, 7.2.4 Profile version field -class Version { -public: - Version() = default; - Version(u8 major, u8 minor_and_bugfix) - : m_major_version(major) - , m_minor_and_bugfix_version(minor_and_bugfix) - { - } - - u8 major_version() const { return m_major_version; } - u8 minor_version() const { return m_minor_and_bugfix_version >> 4; } - u8 bugfix_version() const { return m_minor_and_bugfix_version & 0xf; } - - u8 minor_and_bugfix_version() const { return m_minor_and_bugfix_version; } - -private: - u8 m_major_version = 0; - u8 m_minor_and_bugfix_version = 0; -}; - -// ICC v4, 7.2.11 Profile flags field -class Flags { -public: - Flags(); - - // "The profile flags field contains flags." - Flags(u32); - - u32 bits() const { return m_bits; } - - // "These can indicate various hints for the CMM such as distributed processing and caching options." - // "The least-significant 16 bits are reserved for the ICC." - u16 color_management_module_bits() const { return bits() >> 16; } - u16 icc_bits() const { return bits() & 0xffff; } - - // "Bit position 0: Embedded profile (0 if not embedded, 1 if embedded in file)" - bool is_embedded_in_file() const { return (icc_bits() & 1) != 0; } - - // "Bit position 1: Profile cannot be used independently of the embedded colour data (set to 1 if true, 0 if false)" - // Double negation isn't unconfusing, so this function uses the inverted, positive sense. - bool can_be_used_independently_of_embedded_color_data() const { return (icc_bits() & 2) == 0; } - - static constexpr u32 KnownBitsMask = 3; - -private: - u32 m_bits = 0; -}; - -// ICC v4, 7.2.14 Device attributes field -class DeviceAttributes { -public: - DeviceAttributes(); - - // "The device attributes field shall contain flags used to identify attributes - // unique to the particular device setup for which the profile is applicable." - DeviceAttributes(u64); - - u64 bits() const { return m_bits; } - - // "The least-significant 32 bits of this 64-bit value are defined by the ICC. " - u32 icc_bits() const { return bits() & 0xffff'ffff; } - - // "Notice that bits 0, 1, 2, and 3 describe the media, not the device." - - // "0": "Reflective (0) or transparency (1)" - enum class MediaReflectivity { - Reflective, - Transparent, - }; - MediaReflectivity media_reflectivity() const { return MediaReflectivity(icc_bits() & 1); } - - // "1": "Glossy (0) or matte (1)" - enum class MediaGlossiness { - Glossy, - Matte, - }; - MediaGlossiness media_glossiness() const { return MediaGlossiness((icc_bits() >> 1) & 1); } - - // "2": "Media polarity, positive (0) or negative (1)" - enum class MediaPolarity { - Positive, - Negative, - }; - MediaPolarity media_polarity() const { return MediaPolarity((icc_bits() >> 2) & 1); } - - // "3": "Colour media (0), black & white media (1)" - enum class MediaColor { - Colored, - BlackAndWhite, - }; - MediaColor media_color() const { return MediaColor((icc_bits() >> 3) & 1); } - - // "4 to 31": Reserved (set to binary zero)" - - // "32 to 63": "Use not defined by ICC (vendor specific" - u32 vendor_bits() const { return bits() >> 32; } - - static constexpr u64 KnownBitsMask = 0xf; - -private: - u64 m_bits = 0; -}; - -// Time is in UTC. -// Per spec, month is 1-12, day is 1-31, hours is 0-23, minutes 0-59, seconds 0-59 (i.e. no leap seconds). -// But in practice, some profiles have invalid dates, like 0-0-0 0:0:0. -// For valid profiles, the conversion to time_t will succeed. -struct DateTime { - u16 year = 1970; - u16 month = 1; // One-based. - u16 day = 1; // One-based. - u16 hours = 0; - u16 minutes = 0; - u16 seconds = 0; - - ErrorOr to_time_t() const; - static ErrorOr from_time_t(time_t); -}; - -struct ProfileHeader { - u32 on_disk_size { 0 }; - Optional preferred_cmm_type; - Version version; - DeviceClass device_class {}; - ColorSpace data_color_space {}; - ColorSpace connection_space {}; - DateTime creation_timestamp; - Optional primary_platform {}; - Flags flags; - Optional device_manufacturer; - Optional device_model; - DeviceAttributes device_attributes; - RenderingIntent rendering_intent {}; - XYZ pcs_illuminant; - Optional creator; - Optional id; -}; - -// FIXME: This doesn't belong here. -class MatrixMatrixConversion { -public: - MatrixMatrixConversion(LutCurveType source_red_TRC, - LutCurveType source_green_TRC, - LutCurveType source_blue_TRC, - FloatMatrix3x3 matrix, - LutCurveType destination_red_TRC, - LutCurveType destination_green_TRC, - LutCurveType destination_blue_TRC); - - Color map(FloatVector3) const; - -private: - LutCurveType m_source_red_TRC; - LutCurveType m_source_green_TRC; - LutCurveType m_source_blue_TRC; - FloatMatrix3x3 m_matrix; - LutCurveType m_destination_red_TRC; - LutCurveType m_destination_green_TRC; - LutCurveType m_destination_blue_TRC; -}; - -inline Color MatrixMatrixConversion::map(FloatVector3 in_rgb) const -{ - auto evaluate_curve = [](TagData const& trc, float f) { - if (trc.type() == CurveTagData::Type) - return static_cast(trc).evaluate(f); - return static_cast(trc).evaluate(f); - }; - - auto evaluate_curve_inverse = [](TagData const& trc, float f) { - if (trc.type() == CurveTagData::Type) - return static_cast(trc).evaluate_inverse(f); - return static_cast(trc).evaluate_inverse(f); - }; - - FloatVector3 linear_rgb = { - evaluate_curve(m_source_red_TRC, in_rgb[0]), - evaluate_curve(m_source_green_TRC, in_rgb[1]), - evaluate_curve(m_source_blue_TRC, in_rgb[2]), - }; - linear_rgb = m_matrix * linear_rgb; - - linear_rgb.clamp(0.f, 1.f); - float device_r = evaluate_curve_inverse(m_destination_red_TRC, linear_rgb[0]); - float device_g = evaluate_curve_inverse(m_destination_green_TRC, linear_rgb[1]); - float device_b = evaluate_curve_inverse(m_destination_blue_TRC, linear_rgb[2]); - - u8 out_r = round(255 * device_r); - u8 out_g = round(255 * device_g); - u8 out_b = round(255 * device_b); - - return Color(out_r, out_g, out_b); -} - -class Profile : public RefCounted { -public: - static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes); - static ErrorOr> create(ProfileHeader const& header, OrderedHashMap> tag_table); - - Optional preferred_cmm_type() const { return m_header.preferred_cmm_type; } - Version version() const { return m_header.version; } - DeviceClass device_class() const { return m_header.device_class; } - ColorSpace data_color_space() const { return m_header.data_color_space; } - - // For non-DeviceLink profiles, always PCSXYZ or PCSLAB. - ColorSpace connection_space() const { return m_header.connection_space; } - - u32 on_disk_size() const { return m_header.on_disk_size; } - DateTime creation_timestamp() const { return m_header.creation_timestamp; } - Optional primary_platform() const { return m_header.primary_platform; } - Flags flags() const { return m_header.flags; } - Optional device_manufacturer() const { return m_header.device_manufacturer; } - Optional device_model() const { return m_header.device_model; } - DeviceAttributes device_attributes() const { return m_header.device_attributes; } - RenderingIntent rendering_intent() const { return m_header.rendering_intent; } - XYZ const& pcs_illuminant() const { return m_header.pcs_illuminant; } - Optional creator() const { return m_header.creator; } - Optional const& id() const { return m_header.id; } - - static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes); - - template - void for_each_tag(Callback callback) const - { - for (auto const& tag : m_tag_table) - callback(tag.key, tag.value); - } - - template> Callback> - ErrorOr try_for_each_tag(Callback&& callback) const - { - for (auto const& tag : m_tag_table) - TRY(callback(tag.key, tag.value)); - return {}; - } - - Optional tag_data(TagSignature signature) const - { - return m_tag_table.get(signature).map([](auto it) -> TagData const& { return *it; }); - } - - Optional tag_string_data(TagSignature signature) const; - - size_t tag_count() const { return m_tag_table.size(); } - - // Only versions 2 and 4 are in use. - bool is_v2() const { return version().major_version() == 2; } - bool is_v4() const { return version().major_version() == 4; } - - // FIXME: The color conversion stuff should be in some other class. - - // Converts an 8-bits-per-channel color to the profile connection space. - // The color's number of channels must match number_of_components_in_color_space(data_color_space()). - // Do not call for DeviceLink or NamedColor profiles. (XXX others?) - // Call connection_space() to find out the space the result is in. - ErrorOr to_pcs(ReadonlyBytes) const; - - // Converts from the profile connection space to an 8-bits-per-channel color. - // The notes on `to_pcs()` apply to this too. - ErrorOr from_pcs(Profile const& source_profile, FloatVector3, Bytes) const; - - ErrorOr to_lab(ReadonlyBytes) const; - - ErrorOr convert_image(Bitmap&, Profile const& source_profile) const; - ErrorOr convert_cmyk_image(Bitmap&, CMYKBitmap const&, Profile const& source_profile) const; - - // Only call these if you know that this is an RGB matrix-based profile. - XYZ const& red_matrix_column() const; - XYZ const& green_matrix_column() const; - XYZ const& blue_matrix_column() const; - - Optional matrix_matrix_conversion(Profile const& source_profile) const; - -private: - Profile(ProfileHeader const& header, OrderedHashMap> tag_table) - : m_header(header) - , m_tag_table(move(tag_table)) - { - } - - XYZ const& xyz_data(TagSignature tag) const - { - auto const& data = *m_tag_table.get(tag).value(); - VERIFY(data.type() == XYZTagData::Type); - return static_cast(data).xyz(); - } - - ErrorOr check_required_tags(); - ErrorOr check_tag_types(); - - ProfileHeader m_header; - OrderedHashMap> m_tag_table; - - // FIXME: The color conversion stuff should be in some other class. - ErrorOr to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes) const; - ErrorOr from_pcs_b_to_a(TagData const& tag_data, FloatVector3 const&, Bytes) const; - ErrorOr convert_image_matrix_matrix(Gfx::Bitmap&, MatrixMatrixConversion const&) const; - - // Cached values. - bool m_cached_has_any_a_to_b_tag { false }; - bool m_cached_has_a_to_b0_tag { false }; - bool m_cached_has_any_b_to_a_tag { false }; - bool m_cached_has_b_to_a0_tag { false }; - bool m_cached_has_all_rgb_matrix_tags { false }; - - // Only valid for RGB matrix-based profiles. - ErrorOr xyz_to_rgb_matrix() const; - FloatMatrix3x3 rgb_to_xyz_matrix() const; - - mutable Optional m_cached_xyz_to_rgb_matrix; - - struct OneElementCLUTCache { - Vector key; - FloatVector3 value; - }; - mutable Optional m_to_pcs_clut_cache; -}; - -} - -template<> -struct AK::Formatter : Formatter { - ErrorOr format(FormatBuilder& builder, Gfx::ICC::Version const& version) - { - return Formatter::format(builder, "{}.{}.{}"sv, version.major_version(), version.minor_version(), version.bugfix_version()); - } -}; diff --git a/Libraries/LibGfx/ICC/TagTypes.cpp b/Libraries/LibGfx/ICC/TagTypes.cpp deleted file mode 100644 index f68c5eee1cb3a..0000000000000 --- a/Libraries/LibGfx/ICC/TagTypes.cpp +++ /dev/null @@ -1,1245 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -namespace Gfx::ICC { - -namespace { - -ErrorOr check_reserved(ReadonlyBytes tag_bytes) -{ - if (tag_bytes.size() < 2 * sizeof(u32)) - return Error::from_string_literal("ICC::Profile: Not enough data for tag reserved field"); - - if (*bit_cast const*>(tag_bytes.data() + sizeof(u32)) != 0) - return Error::from_string_literal("ICC::Profile: tag reserved field not 0"); - - return {}; -} - -} - -TagTypeSignature tag_type(ReadonlyBytes tag_bytes) -{ - VERIFY(tag_bytes.size() >= sizeof(u32)); - return *bit_cast const*>(tag_bytes.data()); -} - -StringView ChromaticityTagData::phosphor_or_colorant_type_name(PhosphorOrColorantType phosphor_or_colorant_type) -{ - switch (phosphor_or_colorant_type) { - case PhosphorOrColorantType::Unknown: - return "Unknown"sv; - case PhosphorOrColorantType::ITU_R_BT_709_2: - return "ITU-R BT.709-2"sv; - case PhosphorOrColorantType::SMPTE_RP145: - return "SMPTE RP145"sv; - case PhosphorOrColorantType::EBU_Tech_3213_E: - return "EBU Tech. 3213-E"sv; - case PhosphorOrColorantType::P22: - return "P22"sv; - case PhosphorOrColorantType::P3: - return "P3"sv; - case PhosphorOrColorantType::ITU_R_BT_2020: - return "ITU-R BT.2020"sv; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr> ChromaticityTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.2 chromaticityType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16)) - return Error::from_string_literal("ICC::Profile: chromaticityType has not enough data"); - - u16 number_of_device_channels = *bit_cast const*>(bytes.data() + 8); - PhosphorOrColorantType phosphor_or_colorant_type = *bit_cast const*>(bytes.data() + 10); - - switch (phosphor_or_colorant_type) { - case PhosphorOrColorantType::Unknown: - case PhosphorOrColorantType::ITU_R_BT_709_2: - case PhosphorOrColorantType::SMPTE_RP145: - case PhosphorOrColorantType::EBU_Tech_3213_E: - case PhosphorOrColorantType::P22: - case PhosphorOrColorantType::P3: - case PhosphorOrColorantType::ITU_R_BT_2020: - break; - default: - return Error::from_string_literal("ICC::Profile: chromaticityType invalid phosphor_or_colorant_type"); - } - - // "If the value is 0001h to 0004h, the number of channels shall be three..." - if (phosphor_or_colorant_type != PhosphorOrColorantType::Unknown && number_of_device_channels != 3) - return Error::from_string_literal("ICC::Profile: chromaticityType unexpected number of channels for phosphor_or_colorant_type"); - - if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16) + number_of_device_channels * 2 * sizeof(u16Fixed16Number)) - return Error::from_string_literal("ICC::Profile: chromaticityType has not enough data for xy coordinates"); - - auto* raw_xy_coordinates = bit_cast const*>(bytes.data() + 12); - Vector xy_coordinates; - TRY(xy_coordinates.try_resize(number_of_device_channels)); - for (size_t i = 0; i < number_of_device_channels; ++i) { - xy_coordinates[i].x = U16Fixed16::create_raw(raw_xy_coordinates[2 * i]); - xy_coordinates[i].y = U16Fixed16::create_raw(raw_xy_coordinates[2 * i + 1]); - } - - // FIXME: Once I find files that have phosphor_or_colorant_type != Unknown, check that the values match the values in Table 31. - - return try_make_ref_counted(offset, size, phosphor_or_colorant_type, move(xy_coordinates)); -} - -ErrorOr> CicpTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.3 cicpType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + 4 * sizeof(u8)) - return Error::from_string_literal("ICC::Profile: cicpType has not enough data"); - - u8 color_primaries = bytes[8]; - u8 transfer_characteristics = bytes[9]; - u8 matrix_coefficients = bytes[10]; - u8 video_full_range_flag = bytes[11]; - - return try_make_ref_counted(offset, size, color_primaries, transfer_characteristics, matrix_coefficients, video_full_range_flag); -} - -namespace { - -struct CurveData { - u32 computed_size; - Vector values; -}; - -ErrorOr curve_data_from_bytes(ReadonlyBytes bytes) -{ - // ICC v4, 10.6 curveType - VERIFY(tag_type(bytes) == CurveTagData::Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 3 * sizeof(u32)) - return Error::from_string_literal("ICC::Profile: curveType has not enough data for count"); - u32 count = *bit_cast const*>(bytes.data() + 8); - - u32 computed_size = 3 * sizeof(u32) + count * sizeof(u16); - if (bytes.size() < computed_size) - return Error::from_string_literal("ICC::Profile: curveType has not enough data for curve points"); - - auto* raw_values = bit_cast const*>(bytes.data() + 12); - Vector values; - TRY(values.try_resize(count)); - - for (u32 i = 0; i < count; ++i) - values[i] = raw_values[i]; - - return CurveData { computed_size, move(values) }; -} - -} - -ErrorOr> CurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset) -{ - auto curve_data = TRY(curve_data_from_bytes(bytes)); - return try_make_ref_counted(offset, curve_data.computed_size, move(curve_data.values)); -} - -ErrorOr> CurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - auto curve_data = TRY(curve_data_from_bytes(bytes)); - return try_make_ref_counted(offset, size, move(curve_data.values)); -} - -ErrorOr> Lut16TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.10 lut16Type - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(LUTHeader) + 2 + sizeof(u16)) - return Error::from_string_literal("ICC::Profile: lut16Type has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - if (header.reserved_for_padding != 0) - return Error::from_string_literal("ICC::Profile: lut16Type reserved_for_padding not 0"); - - u16 number_of_input_table_entries = *bit_cast const*>(bytes.data() + 8 + sizeof(LUTHeader)); - u16 number_of_output_table_entries = *bit_cast const*>(bytes.data() + 8 + sizeof(LUTHeader) + 2); - ReadonlyBytes table_bytes = bytes.slice(8 + sizeof(LUTHeader) + 4); - - // "Each input table consists of a minimum of two and a maximum of 4096 uInt16Number integers. - if (number_of_input_table_entries < 2 || number_of_input_table_entries > 4096) - return Error::from_string_literal("ICC::Profile: lut16Type bad number of input table entries"); - - // "Each output table consists of a minimum of two and a maximum of 4096 uInt16Number integers." - if (number_of_output_table_entries < 2 || number_of_output_table_entries > 4096) - return Error::from_string_literal("ICC::Profile: lut16Type bad number of output table entries"); - - EMatrix3x3 e; - for (int i = 0; i < 9; ++i) - e.e[i] = S15Fixed16::create_raw(header.e_parameters[i]); - - u32 input_tables_size = number_of_input_table_entries * header.number_of_input_channels; - u32 output_tables_size = number_of_output_table_entries * header.number_of_output_channels; - u32 clut_values_size = header.number_of_output_channels; - for (int i = 0; i < header.number_of_input_channels; ++i) - clut_values_size *= header.number_of_clut_grid_points; - - if (table_bytes.size() < (input_tables_size + clut_values_size + output_tables_size) * sizeof(u16)) - return Error::from_string_literal("ICC::Profile: lut16Type has not enough data for tables"); - - auto* raw_table_data = bit_cast const*>(table_bytes.data()); - - Vector input_tables; - input_tables.resize(input_tables_size); - for (u32 i = 0; i < input_tables_size; ++i) - input_tables[i] = raw_table_data[i]; - - Vector clut_values; - clut_values.resize(clut_values_size); - for (u32 i = 0; i < clut_values_size; ++i) - clut_values[i] = raw_table_data[input_tables_size + i]; - - Vector output_tables; - output_tables.resize(output_tables_size); - for (u32 i = 0; i < output_tables_size; ++i) - output_tables[i] = raw_table_data[input_tables_size + clut_values_size + i]; - - return try_make_ref_counted(offset, size, e, - header.number_of_input_channels, header.number_of_output_channels, header.number_of_clut_grid_points, - number_of_input_table_entries, number_of_output_table_entries, - move(input_tables), move(clut_values), move(output_tables)); -} - -ErrorOr> Lut8TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.11 lut8Type - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 8 + sizeof(LUTHeader)) - return Error::from_string_literal("ICC::Profile: lut8Type has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - if (header.reserved_for_padding != 0) - return Error::from_string_literal("ICC::Profile: lut16Type reserved_for_padding not 0"); - - u16 number_of_input_table_entries = 256; - u16 number_of_output_table_entries = 256; - ReadonlyBytes table_bytes = bytes.slice(8 + sizeof(LUTHeader)); - - EMatrix3x3 e; - for (int i = 0; i < 9; ++i) - e.e[i] = S15Fixed16::create_raw(header.e_parameters[i]); - - u32 input_tables_size = number_of_input_table_entries * header.number_of_input_channels; - u32 output_tables_size = number_of_output_table_entries * header.number_of_output_channels; - u32 clut_values_size = header.number_of_output_channels; - for (int i = 0; i < header.number_of_input_channels; ++i) - clut_values_size *= header.number_of_clut_grid_points; - - if (table_bytes.size() < input_tables_size + clut_values_size + output_tables_size) - return Error::from_string_literal("ICC::Profile: lut8Type has not enough data for tables"); - - Vector input_tables; - input_tables.resize(input_tables_size); - memcpy(input_tables.data(), table_bytes.data(), input_tables_size); - - Vector clut_values; - clut_values.resize(clut_values_size); - memcpy(clut_values.data(), table_bytes.data() + input_tables_size, clut_values_size); - - Vector output_tables; - output_tables.resize(output_tables_size); - memcpy(output_tables.data(), table_bytes.data() + input_tables_size + clut_values_size, output_tables_size); - - return try_make_ref_counted(offset, size, e, - header.number_of_input_channels, header.number_of_output_channels, header.number_of_clut_grid_points, - number_of_input_table_entries, number_of_output_table_entries, - move(input_tables), move(clut_values), move(output_tables)); -} - -static ErrorOr read_clut_data(ReadonlyBytes bytes, AdvancedLUTHeader const& header) -{ - // Reads a CLUT as described in ICC v4, 10.12.3 CLUT and 10.13.5 CLUT (the two sections are virtually identical). - if (header.offset_to_clut + sizeof(CLUTHeader) > bytes.size()) - return Error::from_string_literal("ICC::Profile: clut out of bounds"); - - if (header.number_of_input_channels >= sizeof(CLUTHeader::number_of_grid_points_in_dimension)) - return Error::from_string_literal("ICC::Profile: clut has too many input channels"); - - auto& clut_header = *bit_cast(bytes.data() + header.offset_to_clut); - - // "Number of grid points in each dimension. Only the first i entries are used, where i is the number of input channels." - Vector number_of_grid_points_in_dimension; - TRY(number_of_grid_points_in_dimension.try_resize(header.number_of_input_channels)); - for (size_t i = 0; i < header.number_of_input_channels; ++i) - number_of_grid_points_in_dimension[i] = clut_header.number_of_grid_points_in_dimension[i]; - - // "Unused entries shall be set to 00h." - for (size_t i = header.number_of_input_channels; i < sizeof(CLUTHeader::number_of_grid_points_in_dimension); ++i) { - if (clut_header.number_of_grid_points_in_dimension[i] != 0) - return Error::from_string_literal("ICC::Profile: unused clut grid point not 0"); - } - - // "Precision of data elements in bytes. Shall be either 01h or 02h." - if (clut_header.precision_of_data_elements != 1 && clut_header.precision_of_data_elements != 2) - return Error::from_string_literal("ICC::Profile: clut invalid data element precision"); - - // "Reserved for padding, shall be set to 0" - for (size_t i = 0; i < sizeof(CLUTHeader::reserved_for_padding); ++i) { - if (clut_header.reserved_for_padding[i] != 0) - return Error::from_string_literal("ICC::Profile: clut reserved for padding not 0"); - } - - // "The size of the CLUT in bytes is (nGrid1 x nGrid2 x…x nGridN) x number of output channels (o) x size of (channel component)." - u32 clut_size = header.number_of_output_channels; - for (u8 grid_size_in_dimension : number_of_grid_points_in_dimension) - clut_size *= grid_size_in_dimension; - - if (header.offset_to_clut + sizeof(CLUTHeader) + clut_size * clut_header.precision_of_data_elements > bytes.size()) - return Error::from_string_literal("ICC::Profile: clut data out of bounds"); - - if (clut_header.precision_of_data_elements == 1) { - auto* raw_values = bytes.data() + header.offset_to_clut + sizeof(CLUTHeader); - Vector values; - TRY(values.try_resize(clut_size)); - for (u32 i = 0; i < clut_size; ++i) - values[i] = raw_values[i]; - return CLUTData { move(number_of_grid_points_in_dimension), move(values) }; - } - - VERIFY(clut_header.precision_of_data_elements == 2); - auto* raw_values = bit_cast const*>(bytes.data() + header.offset_to_clut + sizeof(CLUTHeader)); - Vector values; - TRY(values.try_resize(clut_size)); - for (u32 i = 0; i < clut_size; ++i) - values[i] = raw_values[i]; - return CLUTData { move(number_of_grid_points_in_dimension), move(values) }; -} - -static ErrorOr read_curve(ReadonlyBytes bytes, u32 offset) -{ - // "All tag data elements shall start on a 4-byte boundary (relative to the start of the profile data stream)" - if (offset % 4 != 0) - return Error::from_string_literal("ICC::Profile: lut curve data not aligned"); - - // See read_curves() below. - if (offset + sizeof(u32) > bytes.size()) - return Error::from_string_literal("ICC::Profile: not enough data for lut curve type"); - - ReadonlyBytes tag_bytes = bytes.slice(offset); - auto type = tag_type(tag_bytes); - - if (type == CurveTagData::Type) - return CurveTagData::from_bytes(tag_bytes, offset); - - if (type == ParametricCurveTagData::Type) - return ParametricCurveTagData::from_bytes(tag_bytes, offset); - - return Error::from_string_literal("ICC::Profile: invalid tag type for lut curve"); -} - -static ErrorOr> read_curves(ReadonlyBytes bytes, u32 offset, u32 count) -{ - // Reads "A", "M", or "B" curves from lutAToBType or lutBToAType. They all have the same - // description (ICC v4 10.12.2, 10.12.4, 10.12.6, 10.13.2, 10.13.4, 10.13.6): - // "The curves are stored sequentially, with 00h bytes used for padding between them if needed. - // Each [type] curve is stored as an embedded curveType or a parametricCurveType (see 10.5 or 10.16). The length - // is as indicated by the convention of the respective curve type. Note that the entire tag type, including the tag - // type signature and reserved bytes, is included for each curve." - - // Both types also say: - // "Curve data elements may be shared. For example, the offsets for A, B and M curves can be identical." - // FIXME: Implement sharing curve objects when that happens. (I haven't seen it happen in practice yet.) - // Probably just pass in an offset->curve hashmap and look there first. - - Vector curves; - - for (u32 i = 0; i < count; ++i) { - // This can't call Profile::read_tag() because that requires tag size to be known in advance. - // Some tag types (e.g. textType) depend on this externally stored size. - // curveType and parametricCurveType don't. - // - // Commentary on the ICC spec: It still seems a bit awkward that the way curve tag types are stored here is - // different from how tag types are stored in the main container. Maybe that's so that the A, M, B curves - // can share data better? - // It's also weird that the A curves can't share data for their various curves -- in the main profile, - // redTRCTag, greenTRCTag, and blueTRCTag usually share the same curve, but here this isn't possible. - // Maybe it wouldn't usually happen for profiles that need lutAToBType or lutBToAType tags? - // I would've probably embedded a tag table in this tag and then have the A, M, B offsets store indices - // into that local table. Maybe there's a good reason why that wasn't done, and anyways, the spec is what it is. - auto curve = TRY(read_curve(bytes, offset)); - offset += align_up_to(curve->size(), 4); - TRY(curves.try_append(move(curve))); - } - - return curves; -} - -static bool is_valid_curve(LutCurveType const& curve) -{ - return curve->type() == CurveTagData::Type || curve->type() == ParametricCurveTagData::Type; -} - -bool are_valid_curves(Optional> const& curves) -{ - if (!curves.has_value()) - return true; - - for (auto const& curve : curves.value()) { - if (!is_valid_curve(curve)) - return false; - } - return true; -} - -ErrorOr> LutAToBTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.12 lutAToBType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(AdvancedLUTHeader)) - return Error::from_string_literal("ICC::Profile: lutAToBType has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - if (header.reserved_for_padding != 0) - return Error::from_string_literal("ICC::Profile: lutAToBType reserved_for_padding not 0"); - - // 10.12.2 “A” curves - // "There are the same number of “A” curves as there are input channels. The “A” curves may only be used when - // the CLUT is used. The curves are stored sequentially, with 00h bytes used for padding between them if needed. - // Each “A” curve is stored as an embedded curveType or a parametricCurveType (see 10.5 or 10.16). The length - // is as indicated by the convention of the respective curve type. Note that the entire tag type, including the tag - // type signature and reserved bytes, is included for each curve." - Optional> a_curves; - if (header.offset_to_a_curves) - a_curves = TRY(read_curves(bytes, header.offset_to_a_curves, header.number_of_input_channels)); - - // 10.12.3 CLUT - Optional clut_data; - if (header.offset_to_clut) { - clut_data = TRY(read_clut_data(bytes, header)); - } else if (header.number_of_input_channels != header.number_of_output_channels) { - // "If the number of input channels does not equal the number of output channels, the CLUT shall be present." - return Error::from_string_literal("ICC::Profile: lutAToBType no CLUT despite different number of input and output channels"); - } - - // Follows from the "Only the following combinations are permitted" list in 10.12.1. - if (a_curves.has_value() != clut_data.has_value()) - return Error::from_string_literal("ICC::Profile: lutAToBType must have 'A' curves exactly if it has a CLUT"); - - // 10.12.4 “M” curves - // "There are the same number of “M” curves as there are output channels. The curves are stored sequentially, - // with 00h bytes used for padding between them if needed. Each “M” curve is stored as an embedded curveType - // or a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the respective - // curve type. Note that the entire tag type, including the tag type signature and reserved bytes, is included for - // each curve. The “M” curves may only be used when the matrix is used." - Optional> m_curves; - if (header.offset_to_m_curves) - m_curves = TRY(read_curves(bytes, header.offset_to_m_curves, header.number_of_output_channels)); - - // 10.12.5 Matrix - // "The matrix is organized as a 3 x 4 array. The elements appear in order from e1-e12. The matrix elements are - // each s15Fixed16Numbers." - Optional e; - if (header.offset_to_matrix) { - if (header.offset_to_matrix + 12 * sizeof(s15Fixed16Number) > bytes.size()) - return Error::from_string_literal("ICC::Profile: lutAToBType matrix out of bounds"); - - e = EMatrix3x4 {}; - auto* raw_e = bit_cast const*>(bytes.data() + header.offset_to_matrix); - for (int i = 0; i < 12; ++i) - e->e[i] = S15Fixed16::create_raw(raw_e[i]); - } - - // Follows from the "Only the following combinations are permitted" list in 10.12.1. - if (m_curves.has_value() != e.has_value()) - return Error::from_string_literal("ICC::Profile: lutAToBType must have 'M' curves exactly if it has a matrix"); - - // 10.12.6 “B” curves - // "There are the same number of “B” curves as there are output channels. The curves are stored sequentially, with - // 00h bytes used for padding between them if needed. Each “B” curve is stored as an embedded curveType or a - // parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the respective curve - // type. Note that the entire tag type, including the tag type signature and reserved bytes, are included for each - // curve." - if (!header.offset_to_b_curves) - return Error::from_string_literal("ICC::Profile: lutAToBType without B curves"); - Vector b_curves = TRY(read_curves(bytes, header.offset_to_b_curves, header.number_of_output_channels)); - - return try_make_ref_counted(offset, size, header.number_of_input_channels, header.number_of_output_channels, - move(a_curves), move(clut_data), move(m_curves), e, move(b_curves)); -} - -ErrorOr> LutBToATagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.13 lutBToAType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(AdvancedLUTHeader)) - return Error::from_string_literal("ICC::Profile: lutBToAType has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - if (header.reserved_for_padding != 0) - return Error::from_string_literal("ICC::Profile: lutBToAType reserved_for_padding not 0"); - - // 10.13.2 “B” curves - // "There are the same number of “B” curves as there are input channels. The curves are stored sequentially, with - // 00h bytes used for padding between them if needed. Each “B” curve is stored as an embedded curveType tag - // or a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the proper curve - // type. Note that the entire tag type, including the tag type signature and reserved bytes, is included for each - // curve." - if (!header.offset_to_b_curves) - return Error::from_string_literal("ICC::Profile: lutBToAType without B curves"); - Vector b_curves = TRY(read_curves(bytes, header.offset_to_b_curves, header.number_of_input_channels)); - - // 10.13.3 Matrix - // "The matrix is organized as a 3 x 4 array. The elements of the matrix appear in the type in order from e1 to e12. - // The matrix elements are each s15Fixed16Numbers" - Optional e; - if (header.offset_to_matrix) { - if (header.offset_to_matrix + 12 * sizeof(s15Fixed16Number) > bytes.size()) - return Error::from_string_literal("ICC::Profile: lutBToAType matrix out of bounds"); - - e = EMatrix3x4 {}; - auto* raw_e = bit_cast const*>(bytes.data() + header.offset_to_matrix); - for (int i = 0; i < 12; ++i) - e->e[i] = S15Fixed16::create_raw(raw_e[i]); - } - - // 10.13.4 “M” curves - // "There are the same number of “M” curves as there are input channels. The curves are stored sequentially, with - // 00h bytes used for padding between them if needed. Each “M” curve is stored as an embedded curveType or - // a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the proper curve - // type. Note that the entire tag type, including the tag type signature and reserved bytes, are included for each - // curve. The “M” curves may only be used when the matrix is used." - Optional> m_curves; - if (header.offset_to_m_curves) - m_curves = TRY(read_curves(bytes, header.offset_to_m_curves, header.number_of_input_channels)); - - // Follows from the "Only the following combinations are permitted" list in 10.13.1. - if (e.has_value() != m_curves.has_value()) - return Error::from_string_literal("ICC::Profile: lutBToAType must have matrix exactly if it has 'M' curves"); - - // 10.13.5 CLUT - Optional clut_data; - if (header.offset_to_clut) { - clut_data = TRY(read_clut_data(bytes, header)); - } else if (header.number_of_input_channels != header.number_of_output_channels) { - // "If the number of input channels does not equal the number of output channels, the CLUT shall be present." - return Error::from_string_literal("ICC::Profile: lutAToBType no CLUT despite different number of input and output channels"); - } - - // 10.13.6 “A” curves - // "There are the same number of “A” curves as there are output channels. The “A” curves may only be used when - // the CLUT is used. The curves are stored sequentially, with 00h bytes used for padding between them if needed. - // Each “A” curve is stored as an embedded curveType or a parametricCurveType (see 10.5 or 10.16). The length - // is as indicated by the convention of the proper curve type. Note that the entire tag type, including the tag type - // signature and reserved bytes, is included for each curve." - Optional> a_curves; - if (header.offset_to_a_curves) - a_curves = TRY(read_curves(bytes, header.offset_to_a_curves, header.number_of_output_channels)); - - // Follows from the "Only the following combinations are permitted" list in 10.13.1. - if (clut_data.has_value() != a_curves.has_value()) - return Error::from_string_literal("ICC::Profile: lutBToAType must have A clut exactly if it has 'A' curves"); - - return try_make_ref_counted(offset, size, header.number_of_input_channels, header.number_of_output_channels, - move(b_curves), e, move(m_curves), move(clut_data), move(a_curves)); -} - -ErrorOr> MeasurementTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.14 measurementType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(MeasurementHeader)) - return Error::from_string_literal("ICC::Profile: measurementTag has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - - TRY(validate_standard_observer(header.standard_observer)); - TRY(validate_measurement_geometry(header.measurement_geometry)); - TRY(validate_standard_illuminant(header.standard_illuminant)); - - return try_make_ref_counted(offset, size, header.standard_observer, header.tristimulus_value_for_measurement_backing, - header.measurement_geometry, U16Fixed16::create_raw(header.measurement_flare), header.standard_illuminant); -} - -ErrorOr MeasurementTagData::validate_standard_observer(StandardObserver standard_observer) -{ - switch (standard_observer) { - case StandardObserver::Unknown: - case StandardObserver::CIE_1931_standard_colorimetric_observer: - case StandardObserver::CIE_1964_standard_colorimetric_observer: - return {}; - } - return Error::from_string_literal("ICC::Profile: unknown standard_observer"); -} - -StringView MeasurementTagData::standard_observer_name(StandardObserver standard_observer) -{ - switch (standard_observer) { - case StandardObserver::Unknown: - return "Unknown"sv; - case StandardObserver::CIE_1931_standard_colorimetric_observer: - return "CIE 1931 standard colorimetric observer"sv; - case StandardObserver::CIE_1964_standard_colorimetric_observer: - return "CIE 1964 standard colorimetric observer"sv; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr MeasurementTagData::validate_measurement_geometry(MeasurementGeometry measurement_geometry) -{ - switch (measurement_geometry) { - case MeasurementGeometry::Unknown: - case MeasurementGeometry::Degrees_0_45_or_45_0: - case MeasurementGeometry::Degrees_0_d_or_d_0: - return {}; - } - return Error::from_string_literal("ICC::Profile: unknown measurement_geometry"); -} - -StringView MeasurementTagData::measurement_geometry_name(MeasurementGeometry measurement_geometry) -{ - switch (measurement_geometry) { - case MeasurementGeometry::Unknown: - return "Unknown"sv; - case MeasurementGeometry::Degrees_0_45_or_45_0: - return "0°:45° or 45°:0°"sv; - case MeasurementGeometry::Degrees_0_d_or_d_0: - return "0°:d or d:0°"sv; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr MeasurementTagData::validate_standard_illuminant(StandardIlluminant standard_illuminant) -{ - switch (standard_illuminant) { - case StandardIlluminant::Unknown: - case StandardIlluminant::D50: - case StandardIlluminant::D65: - case StandardIlluminant::D93: - case StandardIlluminant::F2: - case StandardIlluminant::D55: - case StandardIlluminant::A: - case StandardIlluminant::Equi_Power_E: - case StandardIlluminant::F8: - return {}; - } - return Error::from_string_literal("ICC::Profile: unknown standard_illuminant"); -} - -StringView MeasurementTagData::standard_illuminant_name(StandardIlluminant standard_illuminant) -{ - switch (standard_illuminant) { - case StandardIlluminant::Unknown: - return "Unknown"sv; - case StandardIlluminant::D50: - return "D50"sv; - case StandardIlluminant::D65: - return "D65"sv; - case StandardIlluminant::D93: - return "D93"sv; - case StandardIlluminant::F2: - return "F2"sv; - case StandardIlluminant::D55: - return "D55"sv; - case StandardIlluminant::A: - return "A"sv; - case StandardIlluminant::Equi_Power_E: - return "Equi-Power (E)"sv; - case StandardIlluminant::F8: - return "F8"sv; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr> MultiLocalizedUnicodeTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.15 multiLocalizedUnicodeType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - // "Multiple strings within this tag may share storage locations. For example, en/US and en/UK can refer to the - // same string data." - // This implementation makes redundant string copies in that case. - // Most of the time, this costs just a few bytes, so that seems ok. - - if (bytes.size() < 4 * sizeof(u32)) - return Error::from_string_literal("ICC::Profile: multiLocalizedUnicodeType has not enough data"); - - // Table 54 — multiLocalizedUnicodeType - u32 number_of_records = *bit_cast const*>(bytes.data() + 8); - u32 record_size = *bit_cast const*>(bytes.data() + 12); - - // "The fourth field of this tag, the record size, should contain the value 12, which corresponds to the size in bytes - // of each record. Any code that needs to access the nth record should determine the record’s offset by multiplying - // n by the contents of this size field and adding 16. This minor extra effort allows for future expansion of the record - // encoding, should the need arise, without having to define a new tag type." - if (record_size < sizeof(MultiLocalizedUnicodeRawRecord)) - return Error::from_string_literal("ICC::Profile: multiLocalizedUnicodeType record size too small"); - - Checked records_size_in_bytes = number_of_records; - records_size_in_bytes *= record_size; - records_size_in_bytes += 16; - if (records_size_in_bytes.has_overflow() || bytes.size() < records_size_in_bytes.value()) - return Error::from_string_literal("ICC::Profile: multiLocalizedUnicodeType not enough data for records"); - - Vector records; - TRY(records.try_resize(number_of_records)); - - // "For the definition of language codes and country codes, see respectively - // ISO 639-1 and ISO 3166-1. The Unicode strings in storage should be encoded as 16-bit big-endian, UTF-16BE, - // and should not be NULL terminated." - auto& utf_16be_decoder = *TextCodec::decoder_for("utf-16be"sv); - - for (u32 i = 0; i < number_of_records; ++i) { - size_t offset = 16 + i * record_size; - auto record = *bit_cast(bytes.data() + offset); - - records[i].iso_639_1_language_code = record.language_code; - records[i].iso_3166_1_country_code = record.country_code; - - if (record.string_length_in_bytes % 2 != 0) - return Error::from_string_literal("ICC::Profile: multiLocalizedUnicodeType odd UTF-16 byte length"); - - if (static_cast(record.string_offset_in_bytes) + record.string_length_in_bytes > bytes.size()) - return Error::from_string_literal("ICC::Profile: multiLocalizedUnicodeType string offset out of bounds"); - - StringView utf_16be_data { bytes.data() + record.string_offset_in_bytes, record.string_length_in_bytes }; - - // Despite the "should not be NULL terminated" in the spec, some files in the wild have trailing NULLs. - // Fix up this case here, so that application code doesn't have to worry about it. - // (If this wasn't hit in practice, we'd return an Error instead.) - while (utf_16be_data.length() >= 2 && utf_16be_data.ends_with(StringView("\0", 2))) - utf_16be_data = utf_16be_data.substring_view(0, utf_16be_data.length() - 2); - - records[i].text = TRY(utf_16be_decoder.to_utf8(utf_16be_data)); - } - - return try_make_ref_counted(offset, size, move(records)); -} - -unsigned ParametricCurveTagData::parameter_count(FunctionType function_type) -{ - switch (function_type) { - case FunctionType::Type0: - return 1; - case FunctionType::Type1: - return 3; - case FunctionType::Type2: - return 4; - case FunctionType::Type3: - return 5; - case FunctionType::Type4: - return 7; - } - VERIFY_NOT_REACHED(); -} - -ErrorOr> NamedColor2TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.17 namedColor2Type - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(NamedColorHeader)) - return Error::from_string_literal("ICC::Profile: namedColor2Type has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - - Checked record_byte_size = 3; - record_byte_size += header.number_of_device_coordinates_of_each_named_color; - record_byte_size *= sizeof(u16); - record_byte_size += 32; - - Checked end_of_record = record_byte_size; - end_of_record *= header.count_of_named_colors; - end_of_record += 2 * sizeof(u32) + sizeof(NamedColorHeader); - if (end_of_record.has_overflow() || bytes.size() < end_of_record.value()) - return Error::from_string_literal("ICC::Profile: namedColor2Type has not enough color data"); - - auto buffer_to_string = [](u8 const* buffer) -> ErrorOr { - size_t length = strnlen((char const*)buffer, 32); - if (length == 32) - return Error::from_string_literal("ICC::Profile: namedColor2Type string not \\0-terminated"); - for (size_t i = 0; i < length; ++i) - if (buffer[i] >= 128) - return Error::from_string_literal("ICC::Profile: namedColor2Type not 7-bit ASCII"); - return String::from_utf8({ buffer, length }); - }; - - String prefix = TRY(buffer_to_string(header.prefix_for_each_color_name)); - String suffix = TRY(buffer_to_string(header.suffix_for_each_color_name)); - - Vector root_names; - Vector pcs_coordinates; - Vector device_coordinates; - - TRY(root_names.try_resize(header.count_of_named_colors)); - TRY(pcs_coordinates.try_resize(header.count_of_named_colors)); - TRY(device_coordinates.try_resize(header.count_of_named_colors * header.number_of_device_coordinates_of_each_named_color)); - - for (size_t i = 0; i < header.count_of_named_colors; ++i) { - u8 const* root_name = bytes.data() + 8 + sizeof(NamedColorHeader) + i * record_byte_size.value(); - auto* components = bit_cast const*>(root_name + 32); - - root_names[i] = TRY(buffer_to_string(root_name)); - pcs_coordinates[i] = { { { components[0], components[1], components[2] } } }; - for (size_t j = 0; j < header.number_of_device_coordinates_of_each_named_color; ++j) - device_coordinates[i * header.number_of_device_coordinates_of_each_named_color + j] = components[3 + j]; - } - - return try_make_ref_counted(offset, size, header.vendor_specific_flag, header.number_of_device_coordinates_of_each_named_color, - move(prefix), move(suffix), move(root_names), move(pcs_coordinates), move(device_coordinates)); -} - -ErrorOr NamedColor2TagData::color_name(u32 index) const -{ - StringBuilder builder; - builder.append(prefix()); - builder.append(root_name(index)); - builder.append(suffix()); - return builder.to_string(); -} - -namespace { - -struct ParametricCurveData { - u32 computed_size; - ParametricCurveTagData::FunctionType function_type; - Array parameters; -}; - -ErrorOr parametric_curve_data_from_bytes(ReadonlyBytes bytes) -{ - // ICC v4, 10.18 parametricCurveType - VERIFY(tag_type(bytes) == ParametricCurveTagData::Type); - TRY(check_reserved(bytes)); - - // "The parametricCurveType describes a one-dimensional curve by specifying one of a predefined set of functions - // using the parameters." - - if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16)) - return Error::from_string_literal("ICC::Profile: parametricCurveType has not enough data"); - - u16 raw_function_type = *bit_cast const*>(bytes.data() + 8); - u16 reserved = *bit_cast const*>(bytes.data() + 10); - if (reserved != 0) - return Error::from_string_literal("ICC::Profile: parametricCurveType reserved u16 after function type not 0"); - - if (raw_function_type > 4) - return Error::from_string_literal("ICC::Profile: parametricCurveType unknown function type"); - - auto function_type = (ParametricCurveTagData::FunctionType)raw_function_type; - unsigned count = ParametricCurveTagData::parameter_count(function_type); - - u32 computed_size = 2 * sizeof(u32) + 2 * sizeof(u16) + count * sizeof(s15Fixed16Number); - if (bytes.size() < computed_size) - return Error::from_string_literal("ICC::Profile: parametricCurveType has not enough data for parameters"); - - auto* raw_parameters = bit_cast const*>(bytes.data() + 12); - Array parameters; - parameters.fill(0); - for (size_t i = 0; i < count; ++i) - parameters[i] = S15Fixed16::create_raw(raw_parameters[i]); - - return ParametricCurveData { computed_size, function_type, move(parameters) }; -} - -} - -ErrorOr> ParametricCurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset) -{ - auto curve_data = TRY(parametric_curve_data_from_bytes(bytes)); - return try_make_ref_counted(offset, curve_data.computed_size, curve_data.function_type, move(curve_data.parameters)); -} - -ErrorOr> ParametricCurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - auto curve_data = TRY(parametric_curve_data_from_bytes(bytes)); - return try_make_ref_counted(offset, size, curve_data.function_type, move(curve_data.parameters)); -} - -ErrorOr> S15Fixed16ArrayTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.22 s15Fixed16ArrayType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - // "This type represents an array of generic 4-byte (32-bit) fixed point quantity. The number of values is determined - // from the size of the tag." - size_t byte_size = bytes.size() - 8; - if (byte_size % sizeof(s15Fixed16Number) != 0) - return Error::from_string_literal("ICC::Profile: s15Fixed16ArrayType has wrong size"); - - size_t count = byte_size / sizeof(s15Fixed16Number); - auto* raw_values = bit_cast const*>(bytes.data() + 8); - Vector values; - TRY(values.try_resize(count)); - for (size_t i = 0; i < count; ++i) - values[i] = S15Fixed16::create_raw(raw_values[i]); - - return try_make_ref_counted(offset, size, move(values)); -} - -ErrorOr> SignatureTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.23 signatureType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 3 * sizeof(u32)) - return Error::from_string_literal("ICC::Profile: signatureType has not enough data"); - - u32 signature = *bit_cast const*>(bytes.data() + 8); - - return try_make_ref_counted(offset, size, signature); -} - -Optional SignatureTagData::colorimetric_intent_image_state_signature_name(u32 colorimetric_intent_image_state) -{ - // Table 26 — colorimetricIntentImageStateTag signatures - switch (colorimetric_intent_image_state) { - case 0x73636F65: // 'scoe' - return "Scene colorimetry estimates"sv; - case 0x73617065: // 'sape' - return "Scene appearance estimates"sv; - case 0x66706365: // 'fpce' - return "Focal plane colorimetry estimates"sv; - case 0x72686F63: // 'rhoc' - return "Reflection hardcopy original colorimetry"sv; - case 0x72706F63: // 'rpoc' - return "Reflection print output colorimetry"sv; - } - // "Other image state specifications are reserved for future ICC use." - return {}; -} - -Optional SignatureTagData::perceptual_rendering_intent_gamut_signature_name(u32 perceptual_rendering_intent_gamut) -{ - // Table 27 — Perceptual rendering intent gamut - switch (perceptual_rendering_intent_gamut) { - case 0x70726D67: // 'prmg' - return "Perceptual reference medium gamut"sv; - } - // "It is possible that the ICC will define other signature values in the future." - return {}; -} - -Optional SignatureTagData::saturation_rendering_intent_gamut_signature_name(u32 saturation_rendering_intent_gamut) -{ - // Table 28 — Saturation rendering intent gamut - switch (saturation_rendering_intent_gamut) { - case 0x70726D67: // 'prmg' - return "Perceptual reference medium gamut"sv; - } - // "It is possible that the ICC will define other signature values in the future." - return {}; -} - -Optional SignatureTagData::technology_signature_name(u32 technology) -{ - // Table 29 — Technology signatures - switch (technology) { - case 0x6673636E: // 'fscn' - return "Film scanner"sv; - case 0x6463616D: // 'dcam' - return "Digital camera"sv; - case 0x7273636E: // 'rscn' - return "Reflective scanner"sv; - case 0x696A6574: // 'ijet' - return "Ink jet printer"sv; - case 0x74776178: // 'twax' - return "Thermal wax printer"sv; - case 0x6570686F: // 'epho' - return "Electrophotographic printer"sv; - case 0x65737461: // 'esta' - return "Electrostatic printer"sv; - case 0x64737562: // 'dsub' - return "Dye sublimation printer"sv; - case 0x7270686F: // 'rpho' - return "Photographic paper printer"sv; - case 0x6670726E: // 'fprn' - return "Film writer"sv; - case 0x7669646D: // 'vidm' - return "Video monitor"sv; - case 0x76696463: // 'vidc' - return "Video camera"sv; - case 0x706A7476: // 'pjtv' - return "Projection television"sv; - case 0x43525420: // 'CRT ' - return "Cathode ray tube display"sv; - case 0x504D4420: // 'PMD ' - return "Passive matrix display"sv; - case 0x414D4420: // 'AMD ' - return "Active matrix display"sv; - case 0x4C434420: // 'LCD ' - return "Liquid crystal display"sv; - case 0x4F4C4544: // 'OLED' - return "Organic LED display"sv; - case 0x4B504344: // 'KPCD' - return "Photo CD"sv; - case 0x696D6773: // 'imgs' - return "Photographic image setter"sv; - case 0x67726176: // 'grav' - return "Gravure"sv; - case 0x6F666673: // 'offs' - return "Offset lithography"sv; - case 0x73696C6B: // 'silk' - return "Silkscreen"sv; - case 0x666C6578: // 'flex' - return "Flexography"sv; - case 0x6D706673: // 'mpfs' - return "Motion picture film scanner"sv; - case 0x6D706672: // 'mpfr' - return "Motion picture film recorder"sv; - case 0x646D7063: // 'dmpc' - return "Digital motion picture camera"sv; - case 0x64636A70: // 'dcpj' - return "Digital cinema projector"sv; - } - // The spec does *not* say that other values are reserved for future use, but it says that for - // all other tags using signatureType. So return a {} here too instead of VERIFY_NOT_REACHED(). - return {}; -} - -Optional SignatureTagData::name_for_tag(TagSignature tag) -{ - if (tag == colorimetricIntentImageStateTag) - return colorimetric_intent_image_state_signature_name(signature()); - if (tag == perceptualRenderingIntentGamutTag) - return perceptual_rendering_intent_gamut_signature_name(signature()); - if (tag == saturationRenderingIntentGamutTag) - return saturation_rendering_intent_gamut_signature_name(signature()); - if (tag == technologyTag) - return technology_signature_name(signature()); - return {}; -} - -ErrorOr> TextDescriptionTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v2, 6.5.17 textDescriptionType - // textDescriptionType is no longer in the V4 spec. - // In both the V2 and V4 specs, 'desc' is a required tag. In V4, it has type multiLocalizedUnicodeType, - // but in V2 it has type textDescriptionType. Since 'desc' is required, this type is present in every - // V2 icc file, and there are still many V2 files in use. So textDescriptionType is here to stay for now. - // It's a very 90s type, preceding universal adoption of Unicode. - - // "The textDescriptionType is a complex structure that contains three types of text description structures: - // 7-bit ASCII, Unicode and ScriptCode. Since no single standard method for specifying localizable character - // sets exists across the major platform vendors, including all three provides access for the major operating - // systems. The 7-bit ASCII description is to be an invariant, nonlocalizable name for consistent reference. - // It is preferred that both the Unicode and ScriptCode structures be properly localized." - - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - // 7-bit ASCII - - // "ASCII: The count is the length of the string in bytes including the null terminator." - if (bytes.size() < 3 * sizeof(u32)) - return Error::from_string_literal("ICC::Profile: textDescriptionType has not enough data for ASCII size"); - u32 ascii_description_length = *bit_cast const*>(bytes.data() + 8); - - Checked ascii_description_end = 3 * sizeof(u32); - ascii_description_end += ascii_description_length; - if (ascii_description_end.has_overflow() || bytes.size() < ascii_description_end.value()) - return Error::from_string_literal("ICC::Profile: textDescriptionType has not enough data for ASCII description"); - - u8 const* ascii_description_data = bytes.data() + 3 * sizeof(u32); - for (u32 i = 0; i < ascii_description_length; ++i) { - if (ascii_description_data[i] >= 128) - return Error::from_string_literal("ICC::Profile: textDescriptionType ASCII description not 7-bit ASCII"); - } - - if (ascii_description_length == 0) - return Error::from_string_literal("ICC::Profile: textDescriptionType ASCII description length does not include trailing \\0"); - - if (ascii_description_data[ascii_description_length - 1] != '\0') - return Error::from_string_literal("ICC::Profile: textDescriptionType ASCII description not \\0-terminated"); - - StringView ascii_description { ascii_description_data, ascii_description_length - 1 }; - - // Unicode - - Checked unicode_metadata_end = ascii_description_end; - unicode_metadata_end += 2 * sizeof(u32); - if (unicode_metadata_end.has_overflow() || bytes.size() < unicode_metadata_end.value()) - return Error::from_string_literal("ICC::Profile: textDescriptionType has not enough data for Unicode metadata"); - - // "Because the Unicode language code and Unicode count immediately follow the ASCII description, - // their alignment is not correct when the ASCII count is not a multiple of four" - // So we can't use BigEndian here. - u8 const* cursor = ascii_description_data + ascii_description_length; - u32 unicode_language_code = (u32)(cursor[0] << 24) | (u32)(cursor[1] << 16) | (u32)(cursor[2] << 8) | (u32)cursor[3]; - cursor += 4; - - // "Unicode: The count is the number of characters including a Unicode null where a character is always two bytes." - // This implies UCS-2. - u32 unicode_description_length = (u32)(cursor[0] << 24) | (u32)(cursor[1] << 16) | (u32)(cursor[2] << 8) | (u32)cursor[3]; - cursor += 4; - - Checked unicode_desciption_end = unicode_description_length; - unicode_desciption_end *= 2; - unicode_desciption_end += unicode_metadata_end; - if (unicode_desciption_end.has_overflow() || bytes.size() < unicode_desciption_end.value()) - return Error::from_string_literal("ICC::Profile: textDescriptionType has not enough data for Unicode description"); - - u8 const* unicode_description_data = cursor; - cursor += 2 * unicode_description_length; - for (u32 i = 0; i < unicode_description_length; ++i) { - u16 code_point = (u16)(unicode_description_data[2 * i] << 8) | (u16)unicode_description_data[2 * i + 1]; - if (is_unicode_surrogate(code_point)) - return Error::from_string_literal("ICC::Profile: textDescriptionType Unicode description is not valid UCS-2"); - } - - // If Unicode is not native on the platform, then the Unicode language code and Unicode count should be - // filled in as 0, with no data placed in the Unicode localizable profile description area. - Optional unicode_description; - if (unicode_description_length > 0) { - u32 byte_size_without_nul = 2 * (unicode_description_length - 1); - u16 last_code_point = (u16)(unicode_description_data[byte_size_without_nul] << 8) | (u16)unicode_description_data[byte_size_without_nul + 1]; - if (last_code_point != 0) - return Error::from_string_literal("ICC::Profile: textDescriptionType Unicode description not \\0-terminated"); - - StringView utf_16be_data { unicode_description_data, byte_size_without_nul }; - unicode_description = TRY(TextCodec::decoder_for("utf-16be"sv)->to_utf8(utf_16be_data)); - } - - // ScriptCode - - // What is a script code? It's an old, obsolete mac thing. It looks like it's documented in - // https://developer.apple.com/library/archive/documentation/mac/pdf/Text.pdf - // "Script Codes, Language Codes, and Region Codes 1", PDF page 82. - // I haven't found a complete explanation though. PDF page 84 suggests that: - // - There are 16 script codes - // - 0 is Roman, 1 is Japanese, 2 is Chinese, 3 is Korean, 9 is Devanagari - // Roman uses https://en.wikipedia.org/wiki/Mac_OS_Roman as encoding (also on page 89), - // and "All non-Roman script systems include Roman as a subscript" (page 87). - - // Aha, "Script Codes 6" on page 676 has the complete list! There are 32 of them. - // The document mentions that each script code possibly has its own encoding, but I haven't found - // details on the encodings for script codes other than 0 (which uses Mac OS Roman). - // http://www.kreativekorp.com/charset/encoding/ has an unofficial list of old Mac OS encodings, - // but it's not clear to me which script codes map to which encoding. - - // From here on, quotes are from the ICC spec on textDescriptionType again. - - // "The ScriptCode code is misaligned when the ASCII count is odd." - // So don't use BigEndian here. - u16 scriptcode_code = (u16)(cursor[0] << 8) | (u32)cursor[1]; - cursor += 2; - - // "ScriptCode: The count is the length of the string in bytes including the terminating null." - u8 macintosh_description_length = *cursor; - cursor += 1; - - Checked macintosh_description_end = unicode_desciption_end; - macintosh_description_end += 3; - macintosh_description_end += macintosh_description_length; - if (macintosh_description_length > 67 || macintosh_description_end.has_overflow() || macintosh_description_end.value() > bytes.size()) - return Error::from_string_literal("ICC::Profile: textDescriptionType ScriptCode description too long"); - - u8 const* macintosh_description_data = cursor; - - // "If Scriptcode is not native on the platform, then the ScriptCode code and ScriptCode count should be filled - // in as 0. The 67-byte localizable Macintosh profile description should be filled with 0’s." - Optional macintosh_description; - if (macintosh_description_length > 0) { - // ScriptCode is old-timey and a complicated to fully support. Lightroom Classic does write the ScriptCode section of textDescriptionType. - // But supporting only ASCII MacRoman is good enough for those files, and easy to implement, so let's do only that for now. - if (scriptcode_code == 0) { // MacRoman - if (macintosh_description_data[macintosh_description_length - 1] != '\0') - return Error::from_string_literal("ICC::Profile: textDescriptionType ScriptCode not \\0-terminated"); - - macintosh_description = TRY(TextCodec::decoder_for("x-mac-roman"sv)->to_utf8({ macintosh_description_data, (size_t)macintosh_description_length - 1 })); - } else { - dbgln("TODO: ICCProfile textDescriptionType ScriptCode {}, length {}", scriptcode_code, macintosh_description_length); - } - } - - return try_make_ref_counted(offset, size, TRY(String::from_utf8(ascii_description)), unicode_language_code, move(unicode_description), move(macintosh_description)); -} - -ErrorOr> TextTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.24 textType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - // "The textType is a simple text structure that contains a 7-bit ASCII text string. The length of the string is obtained - // by subtracting 8 from the element size portion of the tag itself. This string shall be terminated with a 00h byte." - u32 length = bytes.size() - 8; - - u8 const* text_data = bytes.data() + 8; - for (u32 i = 0; i < length; ++i) { - if (text_data[i] >= 128) - return Error::from_string_literal("ICC::Profile: textType data not 7-bit ASCII"); - } - - if (length == 0) - return Error::from_string_literal("ICC::Profile: textType too short for \\0 byte"); - - if (text_data[length - 1] != '\0') - return Error::from_string_literal("ICC::Profile: textType data not \\0-terminated"); - - return try_make_ref_counted(offset, size, TRY(String::from_utf8(StringView(text_data, length - 1)))); -} - -ErrorOr> ViewingConditionsTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.30 viewingConditionsType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - if (bytes.size() < 2 * sizeof(u32) + sizeof(ViewingConditionsHeader)) - return Error::from_string_literal("ICC::Profile: viewingConditionsType has not enough data"); - - auto& header = *bit_cast(bytes.data() + 8); - - TRY(MeasurementTagData::validate_standard_illuminant(header.illuminant_type)); - - return try_make_ref_counted(offset, size, header.unnormalized_ciexyz_values_for_illuminant, - header.unnormalized_ciexyz_values_for_surround, header.illuminant_type); -} - -ErrorOr> XYZTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) -{ - // ICC v4, 10.31 XYZType - VERIFY(tag_type(bytes) == Type); - TRY(check_reserved(bytes)); - - // "The XYZType contains an array of three encoded values for PCSXYZ, CIEXYZ, or nCIEXYZ values. The - // number of sets of values is determined from the size of the tag." - size_t byte_size = bytes.size() - 8; - if (byte_size % sizeof(XYZNumber) != 0) - return Error::from_string_literal("ICC::Profile: XYZType has wrong size"); - - size_t xyz_count = byte_size / sizeof(XYZNumber); - auto* raw_xyzs = bit_cast(bytes.data() + 8); - Vector xyzs; - TRY(xyzs.try_resize(xyz_count)); - for (size_t i = 0; i < xyz_count; ++i) - xyzs[i] = (XYZ)raw_xyzs[i]; - - return try_make_ref_counted(offset, size, move(xyzs)); -} - -} diff --git a/Libraries/LibGfx/ICC/TagTypes.h b/Libraries/LibGfx/ICC/TagTypes.h deleted file mode 100644 index f62b1d3eff071..0000000000000 --- a/Libraries/LibGfx/ICC/TagTypes.h +++ /dev/null @@ -1,1381 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Gfx::ICC { - -// Does one-dimensional linear interpolation over a lookup table. -// Takes a span of values an x in [0.0, 1.0]. -// Finds the two values in the span closest to x (where x = 0.0 is the span's first element and x = 1.0 the span's last element), -// and linearly interpolates between those two values, assumes all values are the same amount apart. -template -float lerp_1d(ReadonlySpan values, float x) -{ - size_t n = values.size() - 1; - size_t i = min(static_cast(x * n), n - 1); - return mix(static_cast(values[i]), static_cast(values[i + 1]), x * n - i); -} - -// Does multi-dimensional linear interpolation over a lookup table. -// `size(i)` should returns the number of samples in the i'th dimension. -// `sample()` gets a vector where 0 <= i'th coordinate < size(i) and should return the value of the look-up table at that position. -inline FloatVector3 lerp_nd(Function size, Function const&)> sample, ReadonlySpan x) -{ - unsigned left_index[x.size()]; - float factor[x.size()]; - for (size_t i = 0; i < x.size(); ++i) { - unsigned n = size(i) - 1; - float ec = x[i] * n; - left_index[i] = min(static_cast(ec), n - 1); - factor[i] = ec - left_index[i]; - } - - FloatVector3 sample_output {}; - // The i'th bit of mask indicates if the i'th coordinate is rounded up or down. - unsigned coordinates[x.size()]; - ReadonlySpan coordinates_span { coordinates, x.size() }; - for (size_t mask = 0; mask < (1u << x.size()); ++mask) { - float sample_weight = 1.0f; - for (size_t i = 0; i < x.size(); ++i) { - coordinates[i] = left_index[i] + ((mask >> i) & 1u); - sample_weight *= ((mask >> i) & 1u) ? factor[i] : 1.0f - factor[i]; - } - sample_output += sample(coordinates_span) * sample_weight; - } - - return sample_output; -} - -using S15Fixed16 = AK::FixedPoint<16, i32>; -using U16Fixed16 = AK::FixedPoint<16, u32>; - -struct XYZ { - float X { 0 }; - float Y { 0 }; - float Z { 0 }; - - bool operator==(const XYZ&) const = default; -}; - -TagTypeSignature tag_type(ReadonlyBytes tag_bytes); - -class TagData : public RefCounted { -public: - u32 offset() const { return m_offset; } - u32 size() const { return m_size; } - TagTypeSignature type() const { return m_type; } - - virtual ~TagData() = default; - -protected: - TagData(u32 offset, u32 size, TagTypeSignature type) - : m_offset(offset) - , m_size(size) - , m_type(type) - { - } - -private: - u32 m_offset; - u32 m_size; - TagTypeSignature m_type; -}; - -class UnknownTagData : public TagData { -public: - UnknownTagData(u32 offset, u32 size, TagTypeSignature type) - : TagData(offset, size, type) - { - } -}; - -// ICC v4, 10.2 chromaticityType -class ChromaticityTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6368726D }; // 'chrm' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - // ICC v4, Table 31 — Colorant and phosphor encoding - enum class PhosphorOrColorantType : u16 { - Unknown = 0, - ITU_R_BT_709_2 = 1, - SMPTE_RP145 = 2, - EBU_Tech_3213_E = 3, - P22 = 4, - P3 = 5, - ITU_R_BT_2020 = 6, - }; - - static StringView phosphor_or_colorant_type_name(PhosphorOrColorantType); - - struct xyCoordinate { - U16Fixed16 x; - U16Fixed16 y; - }; - - ChromaticityTagData(u32 offset, u32 size, PhosphorOrColorantType phosphor_or_colorant_type, Vector xy_coordinates) - : TagData(offset, size, Type) - , m_phosphor_or_colorant_type(phosphor_or_colorant_type) - , m_xy_coordinates(move(xy_coordinates)) - { - } - - PhosphorOrColorantType phosphor_or_colorant_type() const { return m_phosphor_or_colorant_type; } - Vector xy_coordinates() const { return m_xy_coordinates; } - -private: - PhosphorOrColorantType m_phosphor_or_colorant_type; - Vector m_xy_coordinates; -}; - -// ICC v4, 10.3 cicpType -// "The cicpType specifies Coding-independent code points for video signal type identification." -// See presentations at https://www.color.org/events/HDR_experts.xalter for background. -class CicpTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x63696370 }; // 'cicp' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - CicpTagData(u32 offset, u32 size, u8 color_primaries, u8 transfer_characteristics, u8 matrix_coefficients, u8 video_full_range_flag) - : TagData(offset, size, Type) - , m_color_primaries(color_primaries) - , m_transfer_characteristics(transfer_characteristics) - , m_matrix_coefficients(matrix_coefficients) - , m_video_full_range_flag(video_full_range_flag) - { - } - - // "The fields ColourPrimaries, TransferCharacteristics, MatrixCoefficients, and VideoFullRangeFlag shall be - // encoded as specified in Recommendation ITU-T H.273. Recommendation ITU-T H.273 (ISO/IEC 23091-2) - // provides detailed descriptions of the code values and their interpretation." - u8 color_primaries() const { return m_color_primaries; } - u8 transfer_characteristics() const { return m_transfer_characteristics; } - u8 matrix_coefficients() const { return m_matrix_coefficients; } - u8 video_full_range_flag() const { return m_video_full_range_flag; } - -private: - u8 m_color_primaries; - u8 m_transfer_characteristics; - u8 m_matrix_coefficients; - u8 m_video_full_range_flag; -}; - -// ICC v4, 10.6 curveType -class CurveTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x63757276 }; // 'curv' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset); - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - CurveTagData(u32 offset, u32 size, Vector values) - : TagData(offset, size, Type) - , m_values(move(values)) - { - } - - // "The curveType embodies a one-dimensional function which maps an input value in the domain of the function - // to an output value in the range of the function. The domain and range values are in the range of 0,0 to 1,0. - // - When n is equal to 0, an identity response is assumed. - // - When n is equal to 1, then the curve value shall be interpreted as a gamma value, encoded as a - // u8Fixed8Number. Gamma shall be interpreted as the exponent in the equation y = pow(x,γ) and not as an inverse. - // - When n is greater than 1, the curve values (which embody a sampled one-dimensional function) shall be - // defined as follows: - // - The first entry represents the input value 0,0, the last entry represents the input value 1,0, and intermediate - // entries are uniformly spaced using an increment of 1,0/(n-1). These entries are encoded as uInt16Numbers - // (i.e. the values represented by the entries, which are in the range 0,0 to 1,0 are encoded in the range 0 to - // 65 535). Function values between the entries shall be obtained through linear interpolation." - Vector const& values() const { return m_values; } - - // x must be in [0..1]. - float evaluate(float x) const - { - VERIFY(0.f <= x && x <= 1.f); - - if (values().is_empty()) - return x; - - if (values().size() == 1) - return powf(x, values()[0] / (float)0x100); - - return lerp_1d(values().span(), x) / 65535.0f; - } - - // y must be in [0..1]. - float evaluate_inverse(float y) const - { - VERIFY(0.f <= y && y <= 1.f); - - if (values().is_empty()) - return y; - - if (values().size() == 1) - return powf(y, 1.f / (values()[0] / (float)0x100)); - - // FIXME: Verify somewhere that: - // * values() is non-decreasing - // * values()[0] is 0, values()[values().size() - 1] is 65535 - - // FIXME: Use binary search. - size_t n = values().size() - 1; - size_t i = 0; - for (; i < n; ++i) { - if (values()[i] / 65535.f <= y && y <= values()[i + 1] / 65535.f) - break; - } - - float x1 = i / (float)n; - float y1 = values()[i] / 65535.f; - float x2 = (i + 1) / (float)n; - float y2 = values()[i + 1] / 65535.f; - - // Flat line segment? - if (y1 == y2) - return (x1 + x2) / 2; - - return (y - y1) / (y2 - y1) * (x2 - x1) + x1; // Same as `((y - y1) / (y2 - y1) + i) / (float)n` - } - -private: - Vector m_values; -}; - -struct EMatrix3x3 { - // A row-major 3x3 matrix: - // [ e[0] e[1] e[2] ] - // [ e[3] e[4] e[5] ] * v - // ] e[6] e[7] e[8] ] - S15Fixed16 e[9]; - - S15Fixed16 const& operator[](unsigned i) const - { - VERIFY(i < array_size(e)); - return e[i]; - } -}; - -// ICC v4, 10.10 lut16Type -class Lut16TagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D667432 }; // 'mft2' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - Lut16TagData(u32 offset, u32 size, EMatrix3x3 e, - u8 number_of_input_channels, u8 number_of_output_channels, u8 number_of_clut_grid_points, - u16 number_of_input_table_entries, u16 number_of_output_table_entries, - Vector input_tables, Vector clut_values, Vector output_tables) - : TagData(offset, size, Type) - , m_e(e) - , m_number_of_input_channels(number_of_input_channels) - , m_number_of_output_channels(number_of_output_channels) - , m_number_of_clut_grid_points(number_of_clut_grid_points) - , m_number_of_input_table_entries(number_of_input_table_entries) - , m_number_of_output_table_entries(number_of_output_table_entries) - , m_input_tables(move(input_tables)) - , m_clut_values(move(clut_values)) - , m_output_tables(move(output_tables)) - { - VERIFY(m_input_tables.size() == number_of_input_channels * number_of_input_table_entries); - VERIFY(m_output_tables.size() == number_of_output_channels * number_of_output_table_entries); - - VERIFY(number_of_input_table_entries >= 2); - VERIFY(number_of_input_table_entries <= 4096); - VERIFY(number_of_output_table_entries >= 2); - VERIFY(number_of_output_table_entries <= 4096); - } - - EMatrix3x3 const& e_matrix() const { return m_e; } - - u8 number_of_input_channels() const { return m_number_of_input_channels; } - u8 number_of_output_channels() const { return m_number_of_output_channels; } - u8 number_of_clut_grid_points() const { return m_number_of_clut_grid_points; } - - u16 number_of_input_table_entries() const { return m_number_of_input_table_entries; } - u16 number_of_output_table_entries() const { return m_number_of_output_table_entries; } - - Vector const& input_tables() const { return m_input_tables; } - Vector const& clut_values() const { return m_clut_values; } - Vector const& output_tables() const { return m_output_tables; } - - ErrorOr evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const; - -private: - EMatrix3x3 m_e; - - u8 m_number_of_input_channels; - u8 m_number_of_output_channels; - u8 m_number_of_clut_grid_points; - - u16 m_number_of_input_table_entries; - u16 m_number_of_output_table_entries; - - Vector m_input_tables; - Vector m_clut_values; - Vector m_output_tables; -}; - -// ICC v4, 10.11 lut8Type -class Lut8TagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D667431 }; // 'mft1' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - Lut8TagData(u32 offset, u32 size, EMatrix3x3 e, - u8 number_of_input_channels, u8 number_of_output_channels, u8 number_of_clut_grid_points, - u16 number_of_input_table_entries, u16 number_of_output_table_entries, - Vector input_tables, Vector clut_values, Vector output_tables) - : TagData(offset, size, Type) - , m_e(e) - , m_number_of_input_channels(number_of_input_channels) - , m_number_of_output_channels(number_of_output_channels) - , m_number_of_clut_grid_points(number_of_clut_grid_points) - , m_number_of_input_table_entries(number_of_input_table_entries) - , m_number_of_output_table_entries(number_of_output_table_entries) - , m_input_tables(move(input_tables)) - , m_clut_values(move(clut_values)) - , m_output_tables(move(output_tables)) - { - VERIFY(m_input_tables.size() == number_of_input_channels * number_of_input_table_entries); - VERIFY(m_output_tables.size() == number_of_output_channels * number_of_output_table_entries); - - VERIFY(number_of_input_table_entries == 256); - VERIFY(number_of_output_table_entries == 256); - } - - EMatrix3x3 const& e_matrix() const { return m_e; } - - u8 number_of_input_channels() const { return m_number_of_input_channels; } - u8 number_of_output_channels() const { return m_number_of_output_channels; } - u8 number_of_clut_grid_points() const { return m_number_of_clut_grid_points; } - - u16 number_of_input_table_entries() const { return m_number_of_input_table_entries; } - u16 number_of_output_table_entries() const { return m_number_of_output_table_entries; } - - Vector const& input_tables() const { return m_input_tables; } - Vector const& clut_values() const { return m_clut_values; } - Vector const& output_tables() const { return m_output_tables; } - - ErrorOr evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const; - -private: - EMatrix3x3 m_e; - - u8 m_number_of_input_channels; - u8 m_number_of_output_channels; - u8 m_number_of_clut_grid_points; - - u16 m_number_of_input_table_entries; - u16 m_number_of_output_table_entries; - - Vector m_input_tables; - Vector m_clut_values; - Vector m_output_tables; -}; - -struct EMatrix3x4 { - // A row-major 3x3 matrix followed by a translation vector: - // [ e[0] e[1] e[2] ] [ e[9] ] - // [ e[3] e[4] e[5] ] * v + [ e[10] ] - // [ e[6] e[7] e[8] ] [ e[11] ] - S15Fixed16 e[12]; - - S15Fixed16 const& operator[](unsigned i) const - { - VERIFY(i < array_size(e)); - return e[i]; - } -}; - -struct CLUTData { - Vector number_of_grid_points_in_dimension; - Variant, Vector> values; -}; - -using LutCurveType = NonnullRefPtr; // FIXME: Variant instead? - -bool are_valid_curves(Optional> const& curves); - -// ICC v4, 10.12 lutAToBType -class LutAToBTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D414220 }; // 'mAB ' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - LutAToBTagData(u32 offset, u32 size, u8 number_of_input_channels, u8 number_of_output_channels, - Optional> a_curves, Optional clut, Optional> m_curves, Optional e, Vector b_curves) - : TagData(offset, size, Type) - , m_number_of_input_channels(number_of_input_channels) - , m_number_of_output_channels(number_of_output_channels) - , m_a_curves(move(a_curves)) - , m_clut(move(clut)) - , m_m_curves(move(m_curves)) - , m_e(e) - , m_b_curves(move(b_curves)) - { - VERIFY(!m_a_curves.has_value() || m_a_curves->size() == m_number_of_input_channels); - VERIFY(!m_m_curves.has_value() || m_m_curves->size() == m_number_of_output_channels); - VERIFY(m_b_curves.size() == m_number_of_output_channels); - - VERIFY(number_of_input_channels == number_of_output_channels || m_clut.has_value()); - VERIFY(m_a_curves.has_value() == m_clut.has_value()); - VERIFY(m_m_curves.has_value() == m_e.has_value()); - - VERIFY(are_valid_curves(m_a_curves)); - VERIFY(are_valid_curves(m_m_curves)); - VERIFY(are_valid_curves(m_b_curves)); - } - - u8 number_of_input_channels() const { return m_number_of_input_channels; } - u8 number_of_output_channels() const { return m_number_of_output_channels; } - - Optional> const& a_curves() const { return m_a_curves; } - Optional const& clut() const { return m_clut; } - Optional> const& m_curves() const { return m_m_curves; } - Optional const& e_matrix() const { return m_e; } - Vector const& b_curves() const { return m_b_curves; } - - // Returns the result of the LUT pipeline for u8 inputs. - ErrorOr evaluate(ColorSpace connection_space, ReadonlyBytes) const; - -private: - u8 m_number_of_input_channels; - u8 m_number_of_output_channels; - - // "It is possible to use any or all of these processing elements. At least one processing element shall be included. - // Only the following combinations are permitted: - // - B; - // - M, Matrix, B; - // - A, CLUT, B; - // - A, CLUT, M, Matrix, B." - // This seems to imply that the B curve is not in fact optional. - Optional> m_a_curves; - Optional m_clut; - Optional> m_m_curves; - Optional m_e; - Vector m_b_curves; -}; - -// ICC v4, 10.13 lutBToAType -class LutBToATagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D424120 }; // 'mBA ' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - LutBToATagData(u32 offset, u32 size, u8 number_of_input_channels, u8 number_of_output_channels, - Vector b_curves, Optional e, Optional> m_curves, Optional clut, Optional> a_curves) - : TagData(offset, size, Type) - , m_number_of_input_channels(number_of_input_channels) - , m_number_of_output_channels(number_of_output_channels) - , m_b_curves(move(b_curves)) - , m_e(e) - , m_m_curves(move(m_curves)) - , m_clut(move(clut)) - , m_a_curves(move(a_curves)) - { - VERIFY(m_b_curves.size() == m_number_of_input_channels); - VERIFY(!m_m_curves.has_value() || m_m_curves->size() == m_number_of_input_channels); - VERIFY(!m_a_curves.has_value() || m_a_curves->size() == m_number_of_output_channels); - - VERIFY(m_e.has_value() == m_m_curves.has_value()); - VERIFY(m_clut.has_value() == m_a_curves.has_value()); - VERIFY(number_of_input_channels == number_of_output_channels || m_clut.has_value()); - - VERIFY(are_valid_curves(m_b_curves)); - VERIFY(are_valid_curves(m_m_curves)); - VERIFY(are_valid_curves(m_a_curves)); - } - - u8 number_of_input_channels() const { return m_number_of_input_channels; } - u8 number_of_output_channels() const { return m_number_of_output_channels; } - - Vector const& b_curves() const { return m_b_curves; } - Optional const& e_matrix() const { return m_e; } - Optional> const& m_curves() const { return m_m_curves; } - Optional const& clut() const { return m_clut; } - Optional> const& a_curves() const { return m_a_curves; } - - // Returns the result of the LUT pipeline for u8 outputs. - ErrorOr evaluate(ColorSpace connection_space, FloatVector3 const&, Bytes) const; - -private: - u8 m_number_of_input_channels; - u8 m_number_of_output_channels; - - // "It is possible to use any or all of these processing elements. At least one processing element shall be included. - // Only the following combinations are permitted: - // - B; - // - B, Matrix, M; - // - B, CLUT, A; - // - B, Matrix, M, CLUT, A." - // This seems to imply that the B curve is not in fact optional. - Vector m_b_curves; - Optional m_e; - Optional> m_m_curves; - Optional m_clut; - Optional> m_a_curves; -}; - -// ICC v4, 10.14 measurementType -class MeasurementTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D656173 }; // 'meas' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - // Table 50 — Standard observer encodings - enum class StandardObserver { - Unknown = 0, - CIE_1931_standard_colorimetric_observer = 1, - CIE_1964_standard_colorimetric_observer = 2, - }; - static ErrorOr validate_standard_observer(StandardObserver); - static StringView standard_observer_name(StandardObserver); - - // Table 51 — Measurement geometry encodings - enum class MeasurementGeometry { - Unknown = 0, - Degrees_0_45_or_45_0 = 1, - Degrees_0_d_or_d_0 = 2, - }; - static ErrorOr validate_measurement_geometry(MeasurementGeometry); - static StringView measurement_geometry_name(MeasurementGeometry); - - // Table 53 — Standard illuminant encodings - enum class StandardIlluminant { - Unknown = 0, - D50 = 1, - D65 = 2, - D93 = 3, - F2 = 4, - D55 = 5, - A = 6, - Equi_Power_E = 7, - F8 = 8, - }; - static ErrorOr validate_standard_illuminant(StandardIlluminant); - static StringView standard_illuminant_name(StandardIlluminant); - - MeasurementTagData(u32 offset, u32 size, StandardObserver standard_observer, XYZ tristimulus_value_for_measurement_backing, - MeasurementGeometry measurement_geometry, U16Fixed16 measurement_flare, StandardIlluminant standard_illuminant) - : TagData(offset, size, Type) - , m_standard_observer(standard_observer) - , m_tristimulus_value_for_measurement_backing(tristimulus_value_for_measurement_backing) - , m_measurement_geometry(measurement_geometry) - , m_measurement_flare(measurement_flare) - , m_standard_illuminant(standard_illuminant) - { - } - - StandardObserver standard_observer() const { return m_standard_observer; } - XYZ const& tristimulus_value_for_measurement_backing() const { return m_tristimulus_value_for_measurement_backing; } - MeasurementGeometry measurement_geometry() const { return m_measurement_geometry; } - U16Fixed16 measurement_flare() const { return m_measurement_flare; } - StandardIlluminant standard_illuminant() const { return m_standard_illuminant; } - -private: - StandardObserver m_standard_observer; - XYZ m_tristimulus_value_for_measurement_backing; - MeasurementGeometry m_measurement_geometry; - U16Fixed16 m_measurement_flare; - StandardIlluminant m_standard_illuminant; -}; - -// ICC v4, 10.15 multiLocalizedUnicodeType -class MultiLocalizedUnicodeTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6D6C7563 }; // 'mluc' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - struct Record { - u16 iso_639_1_language_code; - u16 iso_3166_1_country_code; - String text; - }; - - MultiLocalizedUnicodeTagData(u32 offset, u32 size, Vector records) - : TagData(offset, size, Type) - , m_records(move(records)) - { - } - - Vector const& records() const { return m_records; } - -private: - Vector m_records; -}; - -// ICC v4, 10.17 namedColor2Type -class NamedColor2TagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x6E636C32 }; // 'ncl2' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - // "The encoding is the same as the encodings for the PCS colour spaces - // as described in 6.3.4.2 and 10.8. Only PCSXYZ and - // legacy 16-bit PCSLAB encodings are permitted. PCS - // values shall be relative colorimetric." - // (Which I suppose implies this type must not be used in DeviceLink profiles unless - // the device's PCS happens to be PCSXYZ or PCSLAB.) - struct XYZOrLAB { - union { - struct { - u16 x, y, z; - } xyz; - struct { - u16 L, a, b; - } lab; - }; - }; - - NamedColor2TagData(u32 offset, u32 size, u32 vendor_specific_flag, u32 number_of_device_coordinates, String prefix, String suffix, - Vector root_names, Vector pcs_coordinates, Vector device_coordinates) - : TagData(offset, size, Type) - , m_vendor_specific_flag(vendor_specific_flag) - , m_number_of_device_coordinates(number_of_device_coordinates) - , m_prefix(move(prefix)) - , m_suffix(move(suffix)) - , m_root_names(move(root_names)) - , m_pcs_coordinates(move(pcs_coordinates)) - , m_device_coordinates(move(device_coordinates)) - { - VERIFY(root_names.size() == pcs_coordinates.size()); - VERIFY(root_names.size() * number_of_device_coordinates == device_coordinates.size()); - - for (u8 byte : m_prefix.bytes()) - VERIFY(byte < 128); - VERIFY(m_prefix.bytes().size() < 32); - - for (u8 byte : m_suffix.bytes()) - VERIFY(byte < 128); - VERIFY(m_suffix.bytes().size() < 32); - - for (auto const& root_name : m_root_names) { - for (u8 byte : root_name.bytes()) - VERIFY(byte < 128); - VERIFY(root_name.bytes().size() < 32); - } - } - - // "(least-significant 16 bits reserved for ICC use)" - u32 vendor_specific_flag() const { return m_vendor_specific_flag; } - - // "If this field is 0, device coordinates are not provided." - u32 number_of_device_coordinates() const { return m_number_of_device_coordinates; } - - u32 size() const { return m_root_names.size(); } - - // "In order to maintain maximum portability, it is strongly recommended that - // special characters of the 7-bit ASCII set not be used." - String const& prefix() const { return m_prefix; } // "7-bit ASCII" - String const& suffix() const { return m_suffix; } // "7-bit ASCII" - String const& root_name(u32 index) const { return m_root_names[index]; } // "7-bit ASCII" - - // Returns 7-bit ASCII. - ErrorOr color_name(u32 index) const; - - // "The PCS representation corresponds to the header’s PCS field." - XYZOrLAB const& pcs_coordinates(u32 index) const { return m_pcs_coordinates[index]; } - - // "The device representation corresponds to the header’s “data colour space” field." - u16 const* device_coordinates(u32 index) const - { - VERIFY((index + 1) * m_number_of_device_coordinates <= m_device_coordinates.size()); - return m_device_coordinates.data() + index * m_number_of_device_coordinates; - } - -private: - u32 m_vendor_specific_flag; - u32 m_number_of_device_coordinates; - String m_prefix; - String m_suffix; - Vector m_root_names; - Vector m_pcs_coordinates; - Vector m_device_coordinates; -}; - -// ICC v4, 10.18 parametricCurveType -class ParametricCurveTagData : public TagData { -public: - // Table 68 — parametricCurveType function type encoding - enum class FunctionType { - // Y = X**g - Type0, - - // Y = (a*X + b)**g if X >= -b/a - // = 0 else - Type1, - CIE_122_1966 = Type1, - - // Y = (a*X + b)**g + c if X >= -b/a - // = c else - Type2, - IEC_61966_1 = Type2, - - // Y = (a*X + b)**g if X >= d - // = c*X else - Type3, - IEC_61966_2_1 = Type3, - sRGB = Type3, - - // Y = (a*X + b)**g + e if X >= d - // = c*X + f else - Type4, - }; - - // "The domain and range of each function shall be [0,0 1,0]. Any function value outside the range shall be clipped - // to the range of the function." - // "NOTE 1 The parameters selected for a parametric curve can result in complex or undefined values for the input range - // used. This can occur, for example, if d < -b/a. In such cases the behaviour of the curve is undefined." - - static constexpr TagTypeSignature Type { 0x70617261 }; // 'para' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset); - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - ParametricCurveTagData(u32 offset, u32 size, FunctionType function_type, Array parameters) - : TagData(offset, size, Type) - , m_function_type(function_type) - , m_parameters(move(parameters)) - { - } - - FunctionType function_type() const { return m_function_type; } - - static unsigned parameter_count(FunctionType); - - unsigned parameter_count() const { return parameter_count(function_type()); } - S15Fixed16 parameter(size_t i) const - { - VERIFY(i < parameter_count()); - return m_parameters[i]; - } - - S15Fixed16 g() const { return m_parameters[0]; } - S15Fixed16 a() const - { - VERIFY(function_type() >= FunctionType::Type1); - return m_parameters[1]; - } - S15Fixed16 b() const - { - VERIFY(function_type() >= FunctionType::Type1); - return m_parameters[2]; - } - S15Fixed16 c() const - { - VERIFY(function_type() >= FunctionType::Type2); - return m_parameters[3]; - } - S15Fixed16 d() const - { - VERIFY(function_type() >= FunctionType::Type3); - return m_parameters[4]; - } - S15Fixed16 e() const - { - VERIFY(function_type() >= FunctionType::Type4); - return m_parameters[5]; - } - S15Fixed16 f() const - { - VERIFY(function_type() >= FunctionType::Type4); - return m_parameters[6]; - } - - // x must be in [0..1]. - float evaluate(float x) const - { - VERIFY(0.f <= x && x <= 1.f); - - switch (function_type()) { - case FunctionType::Type0: - return powf(x, (float)g()); - case FunctionType::Type1: - if (x >= -(float)b() / (float)a()) - return powf((float)a() * x + (float)b(), (float)g()); - return 0; - case FunctionType::Type2: - if (x >= -(float)b() / (float)a()) - return powf((float)a() * x + (float)b(), (float)g()) + (float)c(); - return (float)c(); - case FunctionType::Type3: - if (x >= (float)d()) - return powf((float)a() * x + (float)b(), (float)g()); - return (float)c() * x; - case FunctionType::Type4: - if (x >= (float)d()) - return powf((float)a() * x + (float)b(), (float)g()) + (float)e(); - return (float)c() * x + (float)f(); - } - VERIFY_NOT_REACHED(); - } - - // y must be in [0..1]. - float evaluate_inverse(float y) const - { - VERIFY(0.f <= y && y <= 1.f); - - // See "Recommendations" section in https://www.color.org/whitepapers/ICC_White_Paper35-Use_of_the_parametricCurveType.pdf - // Requirements for the curve to be non-decreasing: - // * γ > 0 - // * a > 0 for types 1-4 - // * c ≥ 0 for types 3 and 4 - // - // Types 3 and 4 additionally require: - // To prevent negative discontinuities: - // * cd ≤ (ad + b) for type 3 - // * cd + f ≤ (ad + b)^γ + e for type 4 - // To prevent complex numbers: - // * ad + b ≥ 0 - // FIXME: Check these requirements somewhere. - - switch (function_type()) { - case FunctionType::Type0: - return powf(y, 1.f / (float)g()); - case FunctionType::Type1: - return (powf(y, 1.f / (float)g()) - (float)b()) / (float)a(); - case FunctionType::Type2: - // Only defined for Y >= c, so I suppose this requires c <= 0 in practice (?). - return (powf(y - (float)c(), 1.f / (float)g()) - (float)b()) / (float)a(); - case FunctionType::Type3: - if (y >= (float)c() * (float)d()) - return (powf(y, 1.f / (float)g()) - (float)b()) / (float)a(); - return y / (float)c(); - case FunctionType::Type4: - if (y >= (float)c() * (float)d()) - return (powf(y - (float)e(), 1.f / (float)g()) - (float)b()) / (float)a(); - return (y - (float)f()) / (float)c(); - } - VERIFY_NOT_REACHED(); - } - -private: - FunctionType m_function_type; - - // Contains, in this order, g a b c d e f. - // Not all FunctionTypes use all parameters. - Array m_parameters; -}; - -// ICC v4, 10.22 s15Fixed16ArrayType -class S15Fixed16ArrayTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x73663332 }; // 'sf32' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - S15Fixed16ArrayTagData(u32 offset, u32 size, Vector values) - : TagData(offset, size, Type) - , m_values(move(values)) - { - } - - Vector const& values() const { return m_values; } - -private: - Vector m_values; -}; - -// ICC v4, 10.23 signatureType -class SignatureTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x73696720 }; // 'sig ' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - SignatureTagData(u32 offset, u32 size, u32 signature) - : TagData(offset, size, Type) - , m_signature(signature) - { - } - - u32 signature() const { return m_signature; } - - static Optional colorimetric_intent_image_state_signature_name(u32); - static Optional perceptual_rendering_intent_gamut_signature_name(u32); - static Optional saturation_rendering_intent_gamut_signature_name(u32); - static Optional technology_signature_name(u32); - - Optional name_for_tag(TagSignature); - -private: - u32 m_signature; -}; - -// ICC v2, 6.5.17 textDescriptionType -class TextDescriptionTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x64657363 }; // 'desc' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - TextDescriptionTagData(u32 offset, u32 size, String ascii_description, u32 unicode_language_code, Optional unicode_description, Optional macintosh_description) - : TagData(offset, size, Type) - , m_ascii_description(move(ascii_description)) - , m_unicode_language_code(unicode_language_code) - , m_unicode_description(move(unicode_description)) - , m_macintosh_description(move(macintosh_description)) - { - for (u8 byte : m_ascii_description.bytes()) - VERIFY(byte < 128); - } - - // Guaranteed to be 7-bit ASCII. - String const& ascii_description() const { return m_ascii_description; } - - u32 unicode_language_code() const { return m_unicode_language_code; } - Optional const& unicode_description() const { return m_unicode_description; } - - Optional const& macintosh_description() const { return m_macintosh_description; } - -private: - String m_ascii_description; - - u32 m_unicode_language_code { 0 }; - Optional m_unicode_description; - - Optional m_macintosh_description; -}; - -// ICC v4, 10.24 textType -class TextTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x74657874 }; // 'text' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - TextTagData(u32 offset, u32 size, String text) - : TagData(offset, size, Type) - , m_text(move(text)) - { - for (u8 byte : m_text.bytes()) - VERIFY(byte < 128); - } - - // Guaranteed to be 7-bit ASCII. - String const& text() const { return m_text; } - -private: - String m_text; -}; - -// ICC v4, 10.30 viewingConditionsType -class ViewingConditionsTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x76696577 }; // 'view' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - ViewingConditionsTagData(u32 offset, u32 size, XYZ const& unnormalized_ciexyz_values_for_illuminant, - XYZ const& unnormalized_ciexyz_values_for_surround, MeasurementTagData::StandardIlluminant illuminant_type) - : TagData(offset, size, Type) - , m_unnormalized_ciexyz_values_for_illuminant(unnormalized_ciexyz_values_for_illuminant) - , m_unnormalized_ciexyz_values_for_surround(unnormalized_ciexyz_values_for_surround) - , m_illuminant_type(illuminant_type) - { - } - - XYZ const& unnormalized_ciexyz_values_for_illuminant() const { return m_unnormalized_ciexyz_values_for_illuminant; } - XYZ const& unnormalized_ciexyz_values_for_surround() const { return m_unnormalized_ciexyz_values_for_surround; } - MeasurementTagData::StandardIlluminant illuminant_type() const { return m_illuminant_type; } - -private: - XYZ m_unnormalized_ciexyz_values_for_illuminant; // "(in which Y is in cd/m2)" - XYZ m_unnormalized_ciexyz_values_for_surround; // "(in which Y is in cd/m2)" - MeasurementTagData::StandardIlluminant m_illuminant_type; -}; - -// ICC v4, 10.31 XYZType -class XYZTagData : public TagData { -public: - static constexpr TagTypeSignature Type { 0x58595A20 }; // 'XYZ ' - - static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); - - XYZTagData(u32 offset, u32 size, Vector xyzs) - : TagData(offset, size, Type) - , m_xyzs(move(xyzs)) - { - } - - Vector const& xyzs() const { return m_xyzs; } - - XYZ const& xyz() const - { - VERIFY(m_xyzs.size() == 1); - return m_xyzs[0]; - } - -private: - Vector m_xyzs; -}; - -inline ErrorOr Lut16TagData::evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes color_u8) const -{ - // See comment at start of LutAToBTagData::evaluate() for the clipping flow. - VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); - VERIFY(number_of_input_channels() == color_u8.size()); - - // FIXME: This will be wrong once Profile::from_pcs_b_to_a() calls this function too. - VERIFY(number_of_output_channels() == 3); - - // ICC v4, 10.11 lut8Type - // "Data is processed using these elements via the following sequence: - // (matrix) ⇨ (1d input tables) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (1d output tables)" - - Vector color; - for (u8 c : color_u8) - color.append(c / 255.0f); - - // "3 x 3 matrix (which shall be the identity matrix unless the input colour space is PCSXYZ)" - // In practice, it's usually RGB or CMYK. - if (input_space == ColorSpace::PCSXYZ) { - EMatrix3x3 const& e = m_e; - color = Vector { - (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2], - (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2], - (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2], - }; - } - - // "The input tables are arrays of 16-bit unsigned values. Each input table consists of a minimum of two and a maximum of 4096 uInt16Number integers. - // Each input table entry is appropriately normalized to the range 0 to 65535. - // The inputTable is of size (InputChannels x inputTableEntries x 2) bytes. - // When stored in this tag, the one-dimensional lookup tables are packed one after another" - for (size_t c = 0; c < color.size(); ++c) - color[c] = lerp_1d(m_input_tables.span().slice(c * m_number_of_input_table_entries, m_number_of_input_table_entries), color[c]) / 65535.0f; - - // "The CLUT is organized as an i-dimensional array with a given number of grid points in each dimension, - // where i is the number of input channels (input tables) in the transform. - // The dimension corresponding to the first input channel varies least rapidly and - // the dimension corresponding to the last input channel varies most rapidly. - // Each grid point value is an o-byte array, where o is the number of output channels. - // The first sequential byte of the entry contains the function value for the first output function, - // the second sequential byte of the entry contains the function value for the second output function, - // and so on until all the output functions have been supplied." - auto sample = [this](ReadonlySpan const& coordinates) { - size_t stride = 3; - size_t offset = 0; - for (int i = coordinates.size() - 1; i >= 0; --i) { - offset += coordinates[i] * stride; - stride *= m_number_of_clut_grid_points; - } - return FloatVector3 { (float)m_clut_values[offset], (float)m_clut_values[offset + 1], (float)m_clut_values[offset + 2] }; - }; - auto size = [this](size_t) { return m_number_of_clut_grid_points; }; - FloatVector3 output_color = lerp_nd(move(size), move(sample), color) / 65535.0f; - - // "The output tables are arrays of 16-bit unsigned values. Each output table consists of a minimum of two and a maximum of 4096 uInt16Number integers. - // Each output table entry is appropriately normalized to the range 0 to 65535. - // The outputTable is of size (OutputChannels x outputTableEntries x 2) bytes. - // When stored in this tag, the one-dimensional lookup tables are packed one after another" - for (u8 c = 0; c < 3; ++c) - output_color[c] = lerp_1d(m_output_tables.span().slice(c * m_number_of_output_table_entries, m_number_of_output_table_entries), output_color[c]) / 65535.0f; - - if (connection_space == ColorSpace::PCSXYZ) { - // Table 11 - PCSXYZ X, Y or Z encoding - output_color *= 65535 / 32768.0f; - } else { - VERIFY(connection_space == ColorSpace::PCSLAB); - - // ICC v4, 10.10 lut16Type - // Note: lut16Type does _not_ use the encoding in 6.3.4.2 General PCS encoding! - - // "To convert colour values from this tag's legacy 16-bit PCSLAB encoding to the 16-bit PCSLAB encoding defined in 6.3.4.2 (Tables 12 and 13), - // multiply all values with 65 535/65 280 (i.e. FFFFh/FF00h). - // Any colour values that are in the value range of legacy 16-bit PCSLAB encoding, but not in the more recent 16-bit PCSLAB encoding, - // shall be clipped on a per-component basis." - output_color *= 65535.0f / 65280.0f; - - // Table 42 — Legacy PCSLAB L* encoding - output_color[0] = clamp(output_color[0] * 100.0f, 0.0f, 100.0f); - - // Table 43 — Legacy PCSLAB a* or PCSLAB b* encoding - output_color[1] = clamp(output_color[1] * 255.0f - 128.0f, -128.0f, 127.0f); - output_color[2] = clamp(output_color[2] * 255.0f - 128.0f, -128.0f, 127.0f); - } - - return output_color; -} - -inline ErrorOr Lut8TagData::evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes color_u8) const -{ - // See comment at start of LutAToBTagData::evaluate() for the clipping flow. - VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); - VERIFY(number_of_input_channels() == color_u8.size()); - - // FIXME: This will be wrong once Profile::from_pcs_b_to_a() calls this function too. - VERIFY(number_of_output_channels() == 3); - - // ICC v4, 10.11 lut8Type - // "Data is processed using these elements via the following sequence: - // (matrix) ⇨ (1d input tables) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (1d output tables)" - - Vector color; - for (u8 c : color_u8) - color.append(c / 255.0f); - - // "3 x 3 matrix (which shall be the identity matrix unless the input colour space is PCSXYZ)" - // In practice, it's usually RGB or CMYK. - if (input_space == ColorSpace::PCSXYZ) { - EMatrix3x3 const& e = m_e; - color = Vector { - (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2], - (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2], - (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2], - }; - } - - // "The input tables are arrays of uInt8Number values. Each input table consists of 256 uInt8Number integers. - // Each input table entry is appropriately normalized to the range 0 to 255. - // The inputTable is of size (InputChannels x 256) bytes. - // When stored in this tag, the one-dimensional lookup tables are packed one after another" - for (size_t c = 0; c < color.size(); ++c) - color[c] = lerp_1d(m_input_tables.span().slice(c * 256, 256), color[c]) / 255.0f; - - // "The CLUT is organized as an i-dimensional array with a given number of grid points in each dimension, - // where i is the number of input channels (input tables) in the transform. - // The dimension corresponding to the first input channel varies least rapidly and - // the dimension corresponding to the last input channel varies most rapidly. - // Each grid point value is an o-byte array, where o is the number of output channels. - // The first sequential byte of the entry contains the function value for the first output function, - // the second sequential byte of the entry contains the function value for the second output function, - // and so on until all the output functions have been supplied." - auto sample = [this](ReadonlySpan const& coordinates) { - size_t stride = 3; - size_t offset = 0; - for (int i = coordinates.size() - 1; i >= 0; --i) { - offset += coordinates[i] * stride; - stride *= m_number_of_clut_grid_points; - } - return FloatVector3 { (float)m_clut_values[offset], (float)m_clut_values[offset + 1], (float)m_clut_values[offset + 2] }; - }; - auto size = [this](size_t) { return m_number_of_clut_grid_points; }; - FloatVector3 output_color = lerp_nd(move(size), move(sample), color) / 255.0f; - - // "The output tables are arrays of uInt8Number values. Each output table consists of 256 uInt8Number integers. - // Each output table entry is appropriately normalized to the range 0 to 255. - // The outputTable is of size (OutputChannels x 256) bytes. - // When stored in this tag, the one-dimensional lookup tables are packed one after another" - for (u8 c = 0; c < 3; ++c) - output_color[c] = lerp_1d(m_output_tables.span().slice(c * 256, 256), output_color[c]) / 255.0f; - - if (connection_space == ColorSpace::PCSXYZ) { - // "An 8-bit PCSXYZ encoding has not been defined, so the interpretation of a lut8Type in a profile that uses PCSXYZ is implementation specific." - } else { - VERIFY(connection_space == ColorSpace::PCSLAB); - - // ICC v4, 6.3.4.2 General PCS encoding - // Table 12 — PCSLAB L* encoding - output_color[0] *= 100.0f; - - // Table 13 — PCSLAB a* or PCSLAB b* encoding - output_color[1] = output_color[1] * 255.0f - 128.0f; - output_color[2] = output_color[2] * 255.0f - 128.0f; - } - - return output_color; -} - -inline ErrorOr LutAToBTagData::evaluate(ColorSpace connection_space, ReadonlyBytes color_u8) const -{ - VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); - VERIFY(number_of_input_channels() == color_u8.size()); - VERIFY(number_of_output_channels() == 3); - - // ICC v4, 10.12 lutAToBType - // "Data are processed using these elements via the following sequence: - // (“A” curves) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (“M” curves) ⇨ (matrix) ⇨ (“B” curves). - - // "The domain and range of the A and B curves and CLUT are defined to consist of all real numbers between 0,0 and 1,0 inclusive. - // The first entry is located at 0,0, the last entry at 1,0, and intermediate entries are uniformly spaced using an increment of 1,0/(m-1). - // For the A and B curves, m is the number of entries in the table. For the CLUT, m is the number of grid points along each dimension. - // Since the domain and range of the tables are 0,0 to 1,0 it is necessary to convert all device values and PCSLAB values to this numeric range. - // It shall be assumed that the maximum value in each case is set to 1,0 and the minimum value to 0,0 and all intermediate values are - // linearly scaled accordingly." - // Scaling from the full range to 0..1 before a curve and then back after the curve only to scale to 0..1 again before the next curve is a no-op, - // so we only scale back to the full range at the very end of this function. - - auto evaluate_curve = [](LutCurveType const& curve, float f) { - VERIFY(curve->type() == CurveTagData::Type || curve->type() == ParametricCurveTagData::Type); - if (curve->type() == CurveTagData::Type) - return static_cast(*curve).evaluate(f); - return static_cast(*curve).evaluate(f); - }; - - FloatVector3 color; - - VERIFY(m_a_curves.has_value() == m_clut.has_value()); - if (m_a_curves.has_value()) { - Vector in_color; - - auto const& a_curves = m_a_curves.value(); - for (u8 c = 0; c < color_u8.size(); ++c) - in_color.append(evaluate_curve(a_curves[c], color_u8[c] / 255.0f)); - - auto const& clut = m_clut.value(); - auto sample1 = [&clut](Vector const& data, ReadonlySpan const& coordinates) { - size_t stride = 3; - size_t offset = 0; - for (int i = coordinates.size() - 1; i >= 0; --i) { - offset += coordinates[i] * stride; - stride *= clut.number_of_grid_points_in_dimension[i]; - } - return FloatVector3 { (float)data[offset], (float)data[offset + 1], (float)data[offset + 2] }; - }; - auto sample = [&clut, &sample1](ReadonlySpan const& coordinates) { - return clut.values.visit( - [&](Vector const& v) { return sample1(v, coordinates) / 255.0f; }, - [&](Vector const& v) { return sample1(v, coordinates) / 65535.0f; }); - }; - auto size = [&clut](size_t i) { return clut.number_of_grid_points_in_dimension[i]; }; - color = lerp_nd(move(size), move(sample), in_color); - } else { - color = FloatVector3 { color_u8[0] / 255.f, color_u8[1] / 255.f, color_u8[2] / 255.f }; - } - - VERIFY(m_m_curves.has_value() == m_e.has_value()); - if (m_m_curves.has_value()) { - auto const& m_curves = m_m_curves.value(); - color = FloatVector3 { - evaluate_curve(m_curves[0], color[0]), - evaluate_curve(m_curves[1], color[1]), - evaluate_curve(m_curves[2], color[2]) - }; - - // ICC v4, 10.12.5 Matrix - // "The resultant values Y1, Y2 and Y3 shall be clipped to the range 0,0 to 1,0 and used as inputs to the “B” curves." - EMatrix3x4 const& e = m_e.value(); - FloatVector3 new_color = { - (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2] + (float)e[9], - (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2] + (float)e[10], - (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2] + (float)e[11], - }; - color = new_color.clamped(0.f, 1.f); - } - - FloatVector3 output_color { - evaluate_curve(m_b_curves[0], color[0]), - evaluate_curve(m_b_curves[1], color[1]), - evaluate_curve(m_b_curves[2], color[2]) - }; - - // ICC v4, 6.3.4.2 General PCS encoding - if (connection_space == ColorSpace::PCSXYZ) { - // Table 11 - PCSXYZ X, Y or Z encoding - output_color *= 65535 / 32768.0f; - } else { - VERIFY(connection_space == ColorSpace::PCSLAB); - // Table 12 — PCSLAB L* encoding - output_color[0] *= 100.0f; - - // Table 13 — PCSLAB a* or PCSLAB b* encoding - output_color[1] = output_color[1] * 255.0f - 128.0f; - output_color[2] = output_color[2] * 255.0f - 128.0f; - } - return output_color; -} - -inline ErrorOr LutBToATagData::evaluate(ColorSpace connection_space, FloatVector3 const& in_color, Bytes out_bytes) const -{ - VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); - VERIFY(number_of_input_channels() == 3); - VERIFY(number_of_output_channels() == out_bytes.size()); - - // ICC v4, 10.13 lutBToAType - // "Data are processed using these elements via the following sequence: - // (“B” curves) ⇨ (matrix) ⇨ (“M” curves) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (“A” curves)." - - // See comment at start of LutAToBTagData::evaluate() for the clipping flow. - // This function generally is the same as LutAToBTagData::evaluate() upside down. - - auto evaluate_curve = [](LutCurveType const& curve, float f) { - VERIFY(curve->type() == CurveTagData::Type || curve->type() == ParametricCurveTagData::Type); - if (curve->type() == CurveTagData::Type) - return static_cast(*curve).evaluate(f); - return static_cast(*curve).evaluate(f); - }; - - FloatVector3 color; - if (connection_space == ColorSpace::PCSXYZ) { - color = in_color * 32768 / 65535.0f; - } else { - VERIFY(connection_space == ColorSpace::PCSLAB); - color[0] = in_color[0] / 100.0f; - color[1] = (in_color[1] + 128.0f) / 255.0f; - color[2] = (in_color[2] + 128.0f) / 255.0f; - } - - color = FloatVector3 { - evaluate_curve(m_b_curves[0], color[0]), - evaluate_curve(m_b_curves[1], color[1]), - evaluate_curve(m_b_curves[2], color[2]) - }; - - VERIFY(m_e.has_value() == m_m_curves.has_value()); - if (m_e.has_value()) { - // ICC v4, 10.13.3 Matrix - // "The resultant values Y1, Y2 and Y3 shall be clipped to the range 0,0 to 1,0 and used as inputs to the “M” curves." - EMatrix3x4 const& e = m_e.value(); - FloatVector3 new_color = { - (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2] + (float)e[9], - (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2] + (float)e[10], - (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2] + (float)e[11], - }; - color = new_color.clamped(0.f, 1.f); - - auto const& m_curves = m_m_curves.value(); - color = FloatVector3 { - evaluate_curve(m_curves[0], color[0]), - evaluate_curve(m_curves[1], color[1]), - evaluate_curve(m_curves[2], color[2]) - }; - } - - VERIFY(m_clut.has_value() == m_a_curves.has_value()); - if (m_clut.has_value()) { - // FIXME - return Error::from_string_literal("LutBToATagData::evaluate: Not yet implemented when CLUT present"); - } else { - VERIFY(number_of_output_channels() == 3); - out_bytes[0] = round_to(color[0] * 255.0f); - out_bytes[1] = round_to(color[1] * 255.0f); - out_bytes[2] = round_to(color[2] * 255.0f); - } - - return {}; -} - -} - -template<> -struct AK::Formatter : Formatter { - ErrorOr format(FormatBuilder& builder, Gfx::ICC::XYZ const& xyz) - { - return Formatter::format(builder, "X = {}, Y = {}, Z = {}"sv, xyz.X, xyz.Y, xyz.Z); - } -}; diff --git a/Libraries/LibGfx/ICC/Tags.cpp b/Libraries/LibGfx/ICC/Tags.cpp deleted file mode 100644 index 7d8a75f296ee6..0000000000000 --- a/Libraries/LibGfx/ICC/Tags.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Gfx::ICC { - -Optional tag_signature_spec_name(TagSignature tag_signature) -{ - switch (tag_signature) { -#define TAG(name, id) \ - case name: \ - return #name##sv; - ENUMERATE_TAG_SIGNATURES(TAG) -#undef TAG - } - return {}; -} - -} diff --git a/Libraries/LibGfx/ICC/Tags.h b/Libraries/LibGfx/ICC/Tags.h deleted file mode 100644 index abefb64f0a031..0000000000000 --- a/Libraries/LibGfx/ICC/Tags.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include - -namespace Gfx::ICC { - -// ICC v4, 9.2 Tag listing -#define ENUMERATE_TAG_SIGNATURES(TAG) \ - TAG(AToB0Tag, 0x41324230 /* 'A2B0' */) \ - TAG(AToB1Tag, 0x41324231 /* 'A2B1' */) \ - TAG(AToB2Tag, 0x41324232 /* 'A2B2' */) \ - TAG(blueMatrixColumnTag, 0x6258595A /* 'bXYZ' */) \ - TAG(blueTRCTag, 0x62545243 /* 'bTRC' */) \ - TAG(BToA0Tag, 0x42324130 /* 'B2A0' */) \ - TAG(BToA1Tag, 0x42324131 /* 'B2A1' */) \ - TAG(BToA2Tag, 0x42324132 /* 'B2A2' */) \ - TAG(BToD0Tag, 0x42324430 /* 'B2D0' */) \ - TAG(BToD1Tag, 0x42324431 /* 'B2D1' */) \ - TAG(BToD2Tag, 0x42324432 /* 'B2D2' */) \ - TAG(BToD3Tag, 0x42324433 /* 'B2D3' */) \ - TAG(calibrationDateTimeTag, 0x63616C74 /* 'calt' */) \ - TAG(charTargetTag, 0x74617267 /* 'targ' */) \ - TAG(chromaticAdaptationTag, 0x63686164 /* 'chad' */) \ - TAG(chromaticityTag, 0x6368726D /* 'chrm' */) \ - TAG(cicpTag, 0x63696370 /* 'cicp' */) \ - TAG(colorantOrderTag, 0x636C726F /* 'clro' */) \ - TAG(colorantTableTag, 0x636C7274 /* 'clrt' */) \ - TAG(colorantTableOutTag, 0x636C6F74 /* 'clot' */) \ - TAG(colorimetricIntentImageStateTag, 0x63696973 /* 'ciis' */) \ - TAG(copyrightTag, 0x63707274 /* 'cprt' */) \ - TAG(deviceMfgDescTag, 0x646D6E64 /* 'dmnd' */) \ - TAG(deviceModelDescTag, 0x646D6464 /* 'dmdd' */) \ - TAG(DToB0Tag, 0x44324230 /* 'D2B0' */) \ - TAG(DToB1Tag, 0x44324231 /* 'D2B1' */) \ - TAG(DToB2Tag, 0x44324232 /* 'D2B2' */) \ - TAG(DToB3Tag, 0x44324233 /* 'D2B3' */) \ - TAG(gamutTag, 0x67616D74 /* 'gamt' */) \ - TAG(grayTRCTag, 0x6B545243 /* 'kTRC' */) \ - TAG(greenMatrixColumnTag, 0x6758595A /* 'gXYZ' */) \ - TAG(greenTRCTag, 0x67545243 /* 'gTRC' */) \ - TAG(luminanceTag, 0x6C756D69 /* 'lumi' */) \ - TAG(measurementTag, 0x6D656173 /* 'meas' */) \ - TAG(metadataTag, 0x6D657461 /* 'meta' */) \ - TAG(mediaWhitePointTag, 0x77747074 /* 'wtpt' */) \ - TAG(namedColor2Tag, 0x6E636C32 /* 'ncl2' */) \ - TAG(outputResponseTag, 0x72657370 /* 'resp' */) \ - TAG(perceptualRenderingIntentGamutTag, 0x72696730 /* 'rig0' */) \ - TAG(preview0Tag, 0x70726530 /* 'pre0' */) \ - TAG(preview1Tag, 0x70726531 /* 'pre1' */) \ - TAG(preview2Tag, 0x70726532 /* 'pre2' */) \ - TAG(profileDescriptionTag, 0x64657363 /* 'desc' */) \ - TAG(profileSequenceDescTag, 0x70736571 /* 'pseq' */) \ - TAG(profileSequenceIdentifierTag, 0x70736964 /* 'psid' */) \ - TAG(redMatrixColumnTag, 0x7258595A /* 'rXYZ' */) \ - TAG(redTRCTag, 0x72545243 /* 'rTRC' */) \ - TAG(saturationRenderingIntentGamutTag, 0x72696732 /* 'rig2' */) \ - TAG(technologyTag, 0x74656368 /* 'tech' */) \ - TAG(viewingCondDescTag, 0x76756564 /* 'vued' */) \ - TAG(viewingConditionsTag, 0x76696577 /* 'view' */) \ - /* The following tags are v2-only */ \ - TAG(crdInfoTag, 0x63726469 /* 'crdi' */) \ - TAG(deviceSettingsTag, 0x64657673 /* 'devs' */) \ - TAG(mediaBlackPointTag, 0x626B7074 /* 'bkpt' */) \ - TAG(namedColorTag, 0x6E636F6C /* 'ncol' */) \ - TAG(ps2CRD0Tag, 0x70736430 /* 'psd0' */) \ - TAG(ps2CRD1Tag, 0x70736431 /* 'psd1' */) \ - TAG(ps2CRD2Tag, 0x70736432 /* 'psd2' */) \ - TAG(ps2CRD3Tag, 0x70736433 /* 'psd3' */) \ - TAG(ps2CSATag, 0x70733273 /* 'ps2s' */) \ - TAG(ps2RenderingIntentTag, 0x70733269 /* 'ps2i' */) \ - TAG(screeningDescTag, 0x73637264 /* 'scrd' */) \ - TAG(screeningTag, 0x7363726E /* 'scrn' */) \ - TAG(ucrbgTag, 0x62666420 /* 'bfd ' */) - -#define TAG(name, id) constexpr inline TagSignature name { id }; -ENUMERATE_TAG_SIGNATURES(TAG) -#undef TAG - -Optional tag_signature_spec_name(TagSignature); - -} diff --git a/Libraries/LibGfx/ICC/WellKnownProfiles.cpp b/Libraries/LibGfx/ICC/WellKnownProfiles.cpp deleted file mode 100644 index c923ccce7d144..0000000000000 --- a/Libraries/LibGfx/ICC/WellKnownProfiles.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Gfx::ICC { - -static ProfileHeader rgb_header() -{ - ProfileHeader header; - header.version = Version(4, 0x40); - header.device_class = DeviceClass::DisplayDevice; - header.data_color_space = ColorSpace::RGB; - header.connection_space = ColorSpace::PCSXYZ; - header.creation_timestamp = MUST(DateTime::from_time_t(0)); - header.rendering_intent = RenderingIntent::Perceptual; - header.pcs_illuminant = XYZ { 0.9642, 1.0, 0.8249 }; - return header; -} - -static ErrorOr> en_US(StringView text) -{ - Vector records; - TRY(records.try_append({ ('e' << 8) | 'n', ('U' << 8) | 'S', TRY(String::from_utf8(text)) })); - return try_make_ref_counted(0, 0, records); -} - -static ErrorOr> XYZ_data(XYZ xyz) -{ - Vector xyzs; - TRY(xyzs.try_append(xyz)); - return try_make_ref_counted(0, 0, move(xyzs)); -} - -ErrorOr> sRGB_curve() -{ - // Numbers from https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ - Array curve_parameters = { 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045 }; - return try_make_ref_counted(0, 0, ParametricCurveTagData::FunctionType::sRGB, curve_parameters); -} - -ErrorOr> sRGB() -{ - // Returns an sRGB profile. - // https://en.wikipedia.org/wiki/SRGB - - // FIXME: There are many different sRGB ICC profiles in the wild. - // Explain why, and why this picks the numbers it does. - // In the meantime, https://github.com/SerenityOS/serenity/pull/17714 has a few notes. - - auto header = rgb_header(); - - OrderedHashMap> tag_table; - - TRY(tag_table.try_set(profileDescriptionTag, TRY(en_US("SerenityOS sRGB"sv)))); - TRY(tag_table.try_set(copyrightTag, TRY(en_US("Public Domain"sv)))); - - // Transfer function. - auto curve = TRY(sRGB_curve()); - TRY(tag_table.try_set(redTRCTag, curve)); - TRY(tag_table.try_set(greenTRCTag, curve)); - TRY(tag_table.try_set(blueTRCTag, curve)); - - // White point. - // ICC v4, 9.2.36 mediaWhitePointTag: "For displays, the values specified shall be those of the PCS illuminant as defined in 7.2.16." - TRY(tag_table.try_set(mediaWhitePointTag, TRY(XYZ_data(header.pcs_illuminant)))); - - // The chromatic_adaptation_matrix values are from https://www.color.org/chadtag.xalter - // That leads to exactly the S15Fixed16 values in the sRGB profiles in GIMP, Android, RawTherapee (but not in Compact-ICC-Profiles's v4 sRGB profile). - Vector chromatic_adaptation_matrix = { 1.047882, 0.022918, -0.050217, 0.029586, 0.990478, -0.017075, -0.009247, 0.015075, 0.751678 }; - TRY(tag_table.try_set(chromaticAdaptationTag, TRY(try_make_ref_counted(0, 0, move(chromatic_adaptation_matrix))))); - - // The chromaticity values are from https://www.color.org/srgb.pdf - // The chromatic adaptation matrix in that document is slightly different from the one on https://www.color.org/chadtag.xalter, - // so the values in our sRGB profile are currently not fully self-consistent. - // FIXME: Make values self-consistent (probably by using slightly different chromaticities). - TRY(tag_table.try_set(redMatrixColumnTag, TRY(XYZ_data(XYZ { 0.436030342570117, 0.222438466210245, 0.013897440074263 })))); - TRY(tag_table.try_set(greenMatrixColumnTag, TRY(XYZ_data(XYZ { 0.385101860087134, 0.716942745571917, 0.097076381494207 })))); - TRY(tag_table.try_set(blueMatrixColumnTag, TRY(XYZ_data(XYZ { 0.143067806654203, 0.060618777416563, 0.713926257896652 })))); - - return Profile::create(header, move(tag_table)); -} - -} diff --git a/Libraries/LibGfx/ICC/WellKnownProfiles.h b/Libraries/LibGfx/ICC/WellKnownProfiles.h deleted file mode 100644 index 17e2a0a5dcd43..0000000000000 --- a/Libraries/LibGfx/ICC/WellKnownProfiles.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Gfx::ICC { - -class Profile; -class TagData; - -ErrorOr> sRGB(); - -ErrorOr> sRGB_curve(); - -} diff --git a/Meta/CMake/common_options.cmake b/Meta/CMake/common_options.cmake index 52d2fd3a4fd88..72f71003ba65f 100644 --- a/Meta/CMake/common_options.cmake +++ b/Meta/CMake/common_options.cmake @@ -19,7 +19,6 @@ serenity_option(UNDEFINED_BEHAVIOR_IS_FATAL OFF CACHE BOOL "Make undefined behav serenity_option(ENABLE_ALL_THE_DEBUG_MACROS OFF CACHE BOOL "Enable all debug macros to validate they still compile") serenity_option(ENABLE_ALL_DEBUG_FACILITIES OFF CACHE BOOL "Enable all noisy debug symbols and options. Not recommended for normal developer use") -serenity_option(ENABLE_ADOBE_ICC_PROFILES_DOWNLOAD ON CACHE BOOL "Enable download of Adobe's ICC profiles") serenity_option(ENABLE_COMPILETIME_HEADER_CHECK OFF CACHE BOOL "Enable compiletime check that each library header compiles stand-alone") serenity_option(ENABLE_PUBLIC_SUFFIX_DOWNLOAD ON CACHE BOOL "Enable download of the Public Suffix List at build time") diff --git a/Meta/CMake/download_icc_profiles.cmake b/Meta/CMake/download_icc_profiles.cmake deleted file mode 100644 index 39c2182a5a3e0..0000000000000 --- a/Meta/CMake/download_icc_profiles.cmake +++ /dev/null @@ -1,25 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/utils.cmake) - -if (ENABLE_ADOBE_ICC_PROFILES_DOWNLOAD) - set(ADOBE_ICC_PROFILES_PATH "${SERENITY_CACHE_DIR}/AdobeICCProfiles" CACHE PATH "Download location for Adobe ICC profiles") - set(ADOBE_ICC_PROFILES_DATA_URL "https://download.adobe.com/pub/adobe/iccprofiles/win/AdobeICCProfilesCS4Win_end-user.zip") - set(ADOBE_ICC_PROFILES_ZIP_PATH "${ADOBE_ICC_PROFILES_PATH}/adobe-icc-profiles.zip") - if (ENABLE_NETWORK_DOWNLOADS) - download_file("${ADOBE_ICC_PROFILES_DATA_URL}" "${ADOBE_ICC_PROFILES_ZIP_PATH}") - else() - message(STATUS "Skipping download of ${ADOBE_ICC_PROFILES_DATA_URL}, expecting the archive to have been donwloaded to ${ADOBE_ICC_PROFILES_ZIP_PATH}") - endif() - - function(extract_adobe_icc_profiles source path) - if(EXISTS "${ADOBE_ICC_PROFILES_ZIP_PATH}" AND NOT EXISTS "${path}") - file(ARCHIVE_EXTRACT INPUT "${ADOBE_ICC_PROFILES_ZIP_PATH}" DESTINATION "${ADOBE_ICC_PROFILES_PATH}" PATTERNS "${source}") - endif() - endfunction() - - set(ADOBE_ICC_CMYK_SWOP "CMYK/USWebCoatedSWOP.icc") - set(ADOBE_ICC_CMYK_SWOP_PATH "${ADOBE_ICC_PROFILES_PATH}/Adobe ICC Profiles (end-user)/${ADOBE_ICC_CMYK_SWOP}") - extract_adobe_icc_profiles("Adobe ICC Profiles (end-user)/${ADOBE_ICC_CMYK_SWOP}" "${ADOBE_ICC_CMYK_SWOP_PATH}") - - set(ADOBE_ICC_CMYK_SWOP_INSTALL_PATH "${CMAKE_BINARY_DIR}/Root/res/icc/Adobe/${ADOBE_ICC_CMYK_SWOP}") - configure_file("${ADOBE_ICC_CMYK_SWOP_PATH}" "${ADOBE_ICC_CMYK_SWOP_INSTALL_PATH}" COPYONLY) -endif() diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 91022d1f07307..a156a3e07faca 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -452,7 +452,6 @@ lagom_utility(dns SOURCES ../../Utilities/dns.cpp LIBS LibDNS LibMain LibTLS Lib if (ENABLE_GUI_TARGETS) lagom_utility(animation SOURCES ../../Utilities/animation.cpp LIBS LibGfx LibMain) - lagom_utility(icc SOURCES ../../Utilities/icc.cpp LIBS LibGfx LibMain LibURL) lagom_utility(image SOURCES ../../Utilities/image.cpp LIBS LibGfx LibMain) endif() diff --git a/Meta/Lagom/Fuzzers/FuzzICCProfile.cpp b/Meta/Lagom/Fuzzers/FuzzICCProfile.cpp deleted file mode 100644 index 4aa67688b2f0f..0000000000000 --- a/Meta/Lagom/Fuzzers/FuzzICCProfile.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) -{ - AK::set_debug_enabled(false); - (void)Gfx::ICC::Profile::try_load_from_externally_owned_memory({ data, size }); - return 0; -} diff --git a/Meta/Lagom/Fuzzers/fuzzers.cmake b/Meta/Lagom/Fuzzers/fuzzers.cmake index 03a24b9eea4ad..c461e6ba8a649 100644 --- a/Meta/Lagom/Fuzzers/fuzzers.cmake +++ b/Meta/Lagom/Fuzzers/fuzzers.cmake @@ -9,7 +9,6 @@ set(FUZZER_TARGETS GIFLoader GzipDecompression GzipRoundtrip - ICCProfile ICOLoader JPEGLoader Js @@ -56,7 +55,6 @@ set(FUZZER_DEPENDENCIES_ELF LibELF) set(FUZZER_DEPENDENCIES_GIFLoader LibGfx) set(FUZZER_DEPENDENCIES_GzipDecompression LibCompress) set(FUZZER_DEPENDENCIES_GzipRoundtrip LibCompress) -set(FUZZER_DEPENDENCIES_ICCProfile LibGfx) set(FUZZER_DEPENDENCIES_ICOLoader LibGfx) set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx) set(FUZZER_DEPENDENCIES_Js LibJS LibGC) diff --git a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn index 90d0928965c69..657a7237bef74 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn @@ -29,7 +29,6 @@ shared_library("LibGfx") { "BitmapSequence.cpp", "CMYKBitmap.cpp", "Color.cpp", - "DeltaE.cpp", "Font/Font.cpp", "Font/FontData.cpp", "Font/FontDatabase.cpp", @@ -41,12 +40,6 @@ shared_library("LibGfx") { "Font/WOFF2/Loader.cpp", "FontCascadeList.cpp", "GradientPainting.cpp", - "ICC/BinaryWriter.cpp", - "ICC/Enums.cpp", - "ICC/Profile.cpp", - "ICC/TagTypes.cpp", - "ICC/Tags.cpp", - "ICC/WellKnownProfiles.cpp", "ImageFormats/AVIFLoader.cpp", "ImageFormats/AnimationWriter.cpp", "ImageFormats/BMPLoader.cpp", diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index 2666d735d968d..a096845e49c2f 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -1,8 +1,6 @@ set(TEST_SOURCES BenchmarkJPEGLoader.cpp TestColor.cpp - TestDeltaE.cpp - TestICCProfile.cpp TestImageDecoder.cpp TestImageWriter.cpp TestQuad.cpp diff --git a/Tests/LibGfx/TestDeltaE.cpp b/Tests/LibGfx/TestDeltaE.cpp deleted file mode 100644 index fdce878b4d0fb..0000000000000 --- a/Tests/LibGfx/TestDeltaE.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -TEST_CASE(delta_e) -{ - // Test data is from https://hajim.rochester.edu/ece/sites/gsharma/ciede2000/dataNprograms/ciede2000testdata.txt, - // linked to from https://hajim.rochester.edu/ece/sites/gsharma/ciede2000/, which is source [5] in - // https://www.hajim.rochester.edu/ece/sites/gsharma/ciede2000/ciede2000noteCRNA.pdf - struct { - Gfx::CIELAB a; - Gfx::CIELAB b; - float expected_delta; - } test_cases[] = { - { { 50.0000, 2.6772, -79.7751 }, { 50.0000, 0.0000, -82.7485 }, 2.0425 }, - { { 50.0000, 3.1571, -77.2803 }, { 50.0000, 0.0000, -82.7485 }, 2.8615 }, - { { 50.0000, 2.8361, -74.0200 }, { 50.0000, 0.0000, -82.7485 }, 3.4412 }, - { { 50.0000, -1.3802, -84.2814 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 }, - { { 50.0000, -1.1848, -84.8006 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 }, - { { 50.0000, -0.9009, -85.5211 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 }, - { { 50.0000, 0.0000, 0.0000 }, { 50.0000, -1.0000, 2.0000 }, 2.3669 }, - { { 50.0000, -1.0000, 2.0000 }, { 50.0000, 0.0000, 0.0000 }, 2.3669 }, - { { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0009 }, 7.1792 }, - { { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0010 }, 7.1792 }, - { { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0011 }, 7.2195 }, - { { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0012 }, 7.2195 }, - { { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0009, -2.4900 }, 4.8045 }, - { { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0010, -2.4900 }, 4.8045 }, - { { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0011, -2.4900 }, 4.7461 }, - { { 50.0000, 2.5000, 0.0000 }, { 50.0000, 0.0000, -2.5000 }, 4.3065 }, - { { 50.0000, 2.5000, 0.0000 }, { 73.0000, 25.0000, -18.0000 }, 27.1492 }, - { { 50.0000, 2.5000, 0.0000 }, { 61.0000, -5.0000, 29.0000 }, 22.8977 }, - { { 50.0000, 2.5000, 0.0000 }, { 56.0000, -27.0000, -3.0000 }, 31.9030 }, - { { 50.0000, 2.5000, 0.0000 }, { 58.0000, 24.0000, 15.0000 }, 19.4535 }, - { { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.1736, 0.5854 }, 1.0000 }, - { { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.2972, 0.0000 }, 1.0000 }, - { { 50.0000, 2.5000, 0.0000 }, { 50.0000, 1.8634, 0.5757 }, 1.0000 }, - { { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.2592, 0.3350 }, 1.0000 }, - { { 60.2574, -34.0099, 36.2677 }, { 60.4626, -34.1751, 39.4387 }, 1.2644 }, - { { 63.0109, -31.0961, -5.8663 }, { 62.8187, -29.7946, -4.0864 }, 1.2630 }, - { { 61.2901, 3.7196, -5.3901 }, { 61.4292, 2.2480, -4.9620 }, 1.8731 }, - { { 35.0831, -44.1164, 3.7933 }, { 35.0232, -40.0716, 1.5901 }, 1.8645 }, - { { 22.7233, 20.0904, -46.6940 }, { 23.0331, 14.9730, -42.5619 }, 2.0373 }, - { { 36.4612, 47.8580, 18.3852 }, { 36.2715, 50.5065, 21.2231 }, 1.4146 }, - { { 90.8027, -2.0831, 1.4410 }, { 91.1528, -1.6435, 0.0447 }, 1.4441 }, - { { 90.9257, -0.5406, -0.9208 }, { 88.6381, -0.8985, -0.7239 }, 1.5381 }, - { { 6.7747, -0.2908, -2.4247 }, { 5.8714, -0.0985, -2.2286 }, 0.6377 }, - { { 2.0776, 0.0795, -1.1350 }, { 0.9033, -0.0636, -0.5514 }, 0.9082 }, - }; - - for (auto const& test_case : test_cases) { - // The inputs are given with 4 decimal digits, so we can be up to 0.00005 away just to rounding to 4 decimal digits. - EXPECT_APPROXIMATE_WITH_ERROR(Gfx::DeltaE(test_case.a, test_case.b), test_case.expected_delta, 0.00005); - EXPECT_APPROXIMATE_WITH_ERROR(Gfx::DeltaE(test_case.b, test_case.a), test_case.expected_delta, 0.00005); - } -} diff --git a/Tests/LibGfx/TestICCProfile.cpp b/Tests/LibGfx/TestICCProfile.cpp deleted file mode 100644 index 37f8c3c03db63..0000000000000 --- a/Tests/LibGfx/TestICCProfile.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (c) 2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TEST_INPUT(x) ("test-inputs/" x) - -TEST_CASE(png) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/icc-v2.png"sv))); - auto png = MUST(Gfx::PNGImageDecoderPlugin::create(file->bytes())); - auto icc_bytes = MUST(png->icc_data()); - EXPECT(icc_bytes.has_value()); - - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value())); - EXPECT(icc_profile->is_v2()); -} - -TEST_CASE(jpg) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/icc-v4.jpg"sv))); - auto jpg = MUST(Gfx::JPEGImageDecoderPlugin::create(file->bytes())); - auto icc_bytes = MUST(jpg->icc_data()); - EXPECT(icc_bytes.has_value()); - - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value())); - EXPECT(icc_profile->is_v4()); - - icc_profile->for_each_tag([](auto tag_signature, auto tag_data) { - if (tag_signature == Gfx::ICC::profileDescriptionTag) { - // Required per v4 spec, but in practice even v4 files sometimes have TextDescriptionTagData descriptions. Not icc-v4.jpg, though. - EXPECT_EQ(tag_data->type(), Gfx::ICC::MultiLocalizedUnicodeTagData::Type); - auto& multi_localized_unicode = static_cast(*tag_data); - EXPECT_EQ(multi_localized_unicode.records().size(), 1u); - auto& record = multi_localized_unicode.records()[0]; - EXPECT_EQ(record.text, "sRGB built-in"sv); - } - }); -} - -TEST_CASE(webp_extended_lossless) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/extended-lossless.webp"sv))); - auto webp = MUST(Gfx::WebPImageDecoderPlugin::create(file->bytes())); - auto icc_bytes = MUST(webp->icc_data()); - EXPECT(icc_bytes.has_value()); - - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value())); - EXPECT(icc_profile->is_v2()); -} - -TEST_CASE(webp_extended_lossy) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/extended-lossy.webp"sv))); - auto webp = MUST(Gfx::WebPImageDecoderPlugin::create(file->bytes())); - auto icc_bytes = MUST(webp->icc_data()); - EXPECT(icc_bytes.has_value()); - - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value())); - EXPECT(icc_profile->is_v2()); -} - -TEST_CASE(tiff) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/icc.tiff"sv))); - auto tiff = MUST(Gfx::TIFFImageDecoderPlugin::create(file->bytes())); - auto icc_bytes = MUST(tiff->icc_data()); - EXPECT(icc_bytes.has_value()); - - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes.value())); - EXPECT(icc_profile->is_v4()); -} - -TEST_CASE(serialize_icc) -{ - auto file = MUST(Core::MappedFile::map(TEST_INPUT("icc/p3-v4.icc"sv))); - auto icc_profile = MUST(Gfx::ICC::Profile::try_load_from_externally_owned_memory(file->bytes())); - EXPECT(icc_profile->is_v4()); - - auto serialized_bytes = MUST(Gfx::ICC::encode(*icc_profile)); - EXPECT_EQ(serialized_bytes, file->bytes()); -} - -TEST_CASE(built_in_sRGB) -{ - auto sRGB = MUST(Gfx::ICC::sRGB()); - auto serialized_bytes = MUST(Gfx::ICC::encode(sRGB)); - - // We currently exactly match the curve in GIMP's built-in sRGB profile. It's a type 3 'para' curve with 5 parameters. - u32 para[] = { 0x70617261, 0x00000000, 0x00030000, 0x00026666, 0x0000F2A7, 0x00000D59, 0x000013D0, 0x00000A5B }; - for (u32& i : para) - i = AK::convert_between_host_and_big_endian(i); - EXPECT(memmem(serialized_bytes.data(), serialized_bytes.size(), para, sizeof(para)) != nullptr); - - // We currently exactly match the chromatic adaptation matrix in GIMP's (and other's) built-in sRGB profile. - u32 sf32[] = { 0x73663332, 0x00000000, 0x00010C42, 0x000005DE, 0xFFFFF325, 0x00000793, 0x0000FD90, 0xFFFFFBA1, 0xFFFFFDA2, 0x000003DC, 0x0000C06E }; - for (u32& i : sf32) - i = AK::convert_between_host_and_big_endian(i); - EXPECT(memmem(serialized_bytes.data(), serialized_bytes.size(), sf32, sizeof(sf32)) != nullptr); -} - -TEST_CASE(to_pcs) -{ - auto sRGB = MUST(Gfx::ICC::sRGB()); - EXPECT(sRGB->data_color_space() == Gfx::ICC::ColorSpace::RGB); - EXPECT(sRGB->connection_space() == Gfx::ICC::ColorSpace::PCSXYZ); - - auto sRGB_curve_pointer = MUST(Gfx::ICC::sRGB_curve()); - VERIFY(sRGB_curve_pointer->type() == Gfx::ICC::ParametricCurveTagData::Type); - auto const& sRGB_curve = static_cast(*sRGB_curve_pointer); - EXPECT_EQ(sRGB_curve.evaluate(0.f), 0.f); - EXPECT_EQ(sRGB_curve.evaluate(1.f), 1.f); - - auto xyz_from_sRGB = [&sRGB](u8 r, u8 g, u8 b) { - u8 rgb[3] = { r, g, b }; - return MUST(sRGB->to_pcs(rgb)); - }; - - auto vec3_from_xyz = [](Gfx::ICC::XYZ const& xyz) { - return FloatVector3 { xyz.X, xyz.Y, xyz.Z }; - }; - -#define EXPECT_APPROXIMATE_VECTOR3(v1, v2) \ - EXPECT_APPROXIMATE((v1)[0], (v2)[0]); \ - EXPECT_APPROXIMATE((v1)[1], (v2)[1]); \ - EXPECT_APPROXIMATE((v1)[2], (v2)[2]); - - // At 0 and 255, the gamma curve is (exactly) 0 and 1, so these just test the matrix part. - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(0, 0, 0), FloatVector3(0, 0, 0)); - - auto r_xyz = vec3_from_xyz(sRGB->red_matrix_column()); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(255, 0, 0), r_xyz); - - auto g_xyz = vec3_from_xyz(sRGB->green_matrix_column()); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(0, 255, 0), g_xyz); - - auto b_xyz = vec3_from_xyz(sRGB->blue_matrix_column()); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(0, 0, 255), b_xyz); - - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(255, 255, 0), r_xyz + g_xyz); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(255, 0, 255), r_xyz + b_xyz); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(0, 255, 255), g_xyz + b_xyz); - - // FIXME: This should also be equal to sRGB->pcs_illuminant() and to the profiles mediaWhitePointTag, - // but at the moment it's off by a bit too much. See also FIXME in WellKnownProfiles.cpp. - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(255, 255, 255), r_xyz + g_xyz + b_xyz); - - // These test the curve part. - float f64 = sRGB_curve.evaluate(64 / 255.f); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(64, 64, 64), (r_xyz + g_xyz + b_xyz) * f64); - - float f128 = sRGB_curve.evaluate(128 / 255.f); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(128, 128, 128), (r_xyz + g_xyz + b_xyz) * f128); - - // Test for curve and matrix combined. - float f192 = sRGB_curve.evaluate(192 / 255.f); - EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(64, 128, 192), r_xyz * f64 + g_xyz * f128 + b_xyz * f192); -} - -TEST_CASE(from_pcs) -{ - auto sRGB = MUST(Gfx::ICC::sRGB()); - - auto sRGB_curve_pointer = MUST(Gfx::ICC::sRGB_curve()); - VERIFY(sRGB_curve_pointer->type() == Gfx::ICC::ParametricCurveTagData::Type); - auto const& sRGB_curve = static_cast(*sRGB_curve_pointer); - - auto sRGB_from_xyz = [&sRGB](FloatVector3 const& XYZ) { - u8 rgb[3]; - // The first parameter, the source profile, is used to check if the PCS data is XYZ or LAB, - // and what the source whitepoint is. We just need any profile with an XYZ PCS space, - // so passing sRGB as source profile too is fine. - MUST(sRGB->from_pcs(sRGB, XYZ, rgb)); - return Color(rgb[0], rgb[1], rgb[2]); - }; - - auto vec3_from_xyz = [](Gfx::ICC::XYZ const& xyz) { - return FloatVector3 { xyz.X, xyz.Y, xyz.Z }; - }; - - // At 0 and 255, the gamma curve is (exactly) 0 and 1, so these just test the matrix part. - EXPECT_EQ(sRGB_from_xyz(FloatVector3 { 0, 0, 0 }), Color(0, 0, 0)); - - auto r_xyz = vec3_from_xyz(sRGB->red_matrix_column()); - EXPECT_EQ(sRGB_from_xyz(r_xyz), Color(255, 0, 0)); - - auto g_xyz = vec3_from_xyz(sRGB->green_matrix_column()); - EXPECT_EQ(sRGB_from_xyz(g_xyz), Color(0, 255, 0)); - - auto b_xyz = vec3_from_xyz(sRGB->blue_matrix_column()); - EXPECT_EQ(sRGB_from_xyz(b_xyz), Color(0, 0, 255)); - - EXPECT_EQ(sRGB_from_xyz(r_xyz + g_xyz), Color(255, 255, 0)); - EXPECT_EQ(sRGB_from_xyz(r_xyz + b_xyz), Color(255, 0, 255)); - EXPECT_EQ(sRGB_from_xyz(g_xyz + b_xyz), Color(0, 255, 255)); - EXPECT_EQ(sRGB_from_xyz(r_xyz + g_xyz + b_xyz), Color(255, 255, 255)); - - // Test the inverse curve transform. - float f64 = sRGB_curve.evaluate(64 / 255.f); - EXPECT_EQ(sRGB_from_xyz((r_xyz + g_xyz + b_xyz) * f64), Color(64, 64, 64)); - - float f128 = sRGB_curve.evaluate(128 / 255.f); - EXPECT_EQ(sRGB_from_xyz((r_xyz + g_xyz + b_xyz) * f128), Color(128, 128, 128)); - - // Test for curve and matrix combined. - float f192 = sRGB_curve.evaluate(192 / 255.f); - EXPECT_EQ(sRGB_from_xyz(r_xyz * f64 + g_xyz * f128 + b_xyz * f192), Color(64, 128, 192)); -} - -TEST_CASE(to_lab) -{ - auto sRGB = MUST(Gfx::ICC::sRGB()); - auto lab_from_sRGB = [&sRGB](u8 r, u8 g, u8 b) { - u8 rgb[3] = { r, g, b }; - return MUST(sRGB->to_lab(rgb)); - }; - - // The `expected` numbers are from https://colorjs.io/notebook/ for this snippet of code: - // new Color("srgb", [0, 0, 0]).lab.toString(); - // - // new Color("srgb", [1, 0, 0]).lab.toString(); - // new Color("srgb", [0, 1, 0]).lab.toString(); - // new Color("srgb", [0, 0, 1]).lab.toString(); - // - // new Color("srgb", [1, 1, 0]).lab.toString(); - // new Color("srgb", [1, 0, 1]).lab.toString(); - // new Color("srgb", [0, 1, 1]).lab.toString(); - // - // new Color("srgb", [1, 1, 1]).lab.toString(); - - Gfx::CIELAB expected[] = { - { 0, 0, 0 }, - { 54.29054294696968, 80.80492033462421, 69.89098825896275 }, - { 87.81853633115202, -79.27108223854806, 80.99459785152247 }, - { 29.56829715344471, 68.28740665215547, -112.02971798617645 }, - { 97.60701009682253, -15.749846639252663, 93.39361164266089 }, - { 60.16894098715946, 93.53959546199253, -60.50080231921204 }, - { 90.66601315791455, -50.65651077286893, -14.961666625736525 }, - { 100.00000139649632, -0.000007807961277528364, 0.000006766250648659877 }, - }; - - // We're off by more than the default EXPECT_APPROXIMATE() error, so use EXPECT_APPROXIMATE_WITH_ERROR(). - // The difference is not too bad: ranges for L*, a*, b* are [0, 100], [-125, 125], [-125, 125], - // so this is an error of considerably less than 0.1 for u8 channels. -#define EXPECT_APPROXIMATE_LAB(l1, l2) \ - EXPECT_APPROXIMATE_WITH_ERROR((l1).L, (l2).L, 0.01); \ - EXPECT_APPROXIMATE_WITH_ERROR((l1).a, (l2).a, 0.03); \ - EXPECT_APPROXIMATE_WITH_ERROR((l1).b, (l2).b, 0.02); - - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 0, 0), expected[0]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 0, 0), expected[1]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 255, 0), expected[2]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 0, 255), expected[3]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 255, 0), expected[4]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 0, 255), expected[5]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 255, 255), expected[6]); - EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 255, 255), expected[7]); -} - -TEST_CASE(malformed_profile) -{ - Array test_inputs = { - TEST_INPUT("icc/oss-fuzz-testcase-57426.icc"sv), - TEST_INPUT("icc/oss-fuzz-testcase-59551.icc"sv), - TEST_INPUT("icc/oss-fuzz-testcase-60281.icc"sv) - }; - - for (auto test_input : test_inputs) { - auto file = MUST(Core::MappedFile::map(test_input)); - auto profile_or_error = Gfx::ICC::Profile::try_load_from_externally_owned_memory(file->bytes()); - EXPECT(profile_or_error.is_error()); - } -} diff --git a/Tests/LibGfx/TestImageWriter.cpp b/Tests/LibGfx/TestImageWriter.cpp index 6e53bf5e87e9d..64198333cabbf 100644 --- a/Tests/LibGfx/TestImageWriter.cpp +++ b/Tests/LibGfx/TestImageWriter.cpp @@ -6,9 +6,6 @@ #include #include -#include -#include -#include #include #include #include @@ -188,23 +185,6 @@ TEST_CASE(test_webp_color_indexing_transform_single_channel) } } -TEST_CASE(test_webp_icc) -{ - auto sRGB_icc_profile = MUST(Gfx::ICC::sRGB()); - auto sRGB_icc_data = MUST(Gfx::ICC::encode(sRGB_icc_profile)); - - auto rgba_bitmap = TRY_OR_FAIL(create_test_rgba_bitmap()); - Gfx::WebPEncoderOptions options; - options.icc_data = sRGB_icc_data; - auto encoded_rgba_bitmap = TRY_OR_FAIL((encode_bitmap(rgba_bitmap, options))); - - auto decoded_rgba_plugin = TRY_OR_FAIL(Gfx::WebPImageDecoderPlugin::create(encoded_rgba_bitmap)); - expect_bitmaps_equal(*TRY_OR_FAIL(expect_single_frame_of_size(*decoded_rgba_plugin, rgba_bitmap->size())), rgba_bitmap); - auto decoded_rgba_profile = TRY_OR_FAIL(Gfx::ICC::Profile::try_load_from_externally_owned_memory(TRY_OR_FAIL(decoded_rgba_plugin->icc_data()).value())); - auto reencoded_icc_data = TRY_OR_FAIL(Gfx::ICC::encode(decoded_rgba_profile)); - EXPECT_EQ(sRGB_icc_data, reencoded_icc_data); -} - TEST_CASE(test_webp_animation) { auto rgb_bitmap = TRY_OR_FAIL(create_test_rgb_bitmap()); diff --git a/Utilities/icc.cpp b/Utilities/icc.cpp deleted file mode 100644 index cbfd02e274273..0000000000000 --- a/Utilities/icc.cpp +++ /dev/null @@ -1,605 +0,0 @@ -/* - * Copyright (c) 2022-2023, Nico Weber - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -template -static ErrorOr hyperlink(URL::URL const& target, T const& label) -{ - return String::formatted("\033]8;;{}\033\\{}\033]8;;\033\\", target, label); -} - -template -static void out_optional(char const* label, Optional const& optional) -{ - out("{}: ", label); - if (optional.has_value()) - outln("{}", *optional); - else - outln("(not set)"); -} - -static void out_curve(Gfx::ICC::CurveTagData const& curve, int indent_amount) -{ - if (curve.values().is_empty()) { - outln("{: >{}}identity curve", "", indent_amount); - } else if (curve.values().size() == 1) { - outln("{: >{}}gamma: {}", "", indent_amount, AK::FixedPoint<8, u16>::create_raw(curve.values()[0])); - } else { - // FIXME: Maybe print the actual points if -v is passed? - outln("{: >{}}curve with {} points", "", indent_amount, curve.values().size()); - } -} - -static void out_parametric_curve(Gfx::ICC::ParametricCurveTagData const& parametric_curve, int indent_amount) -{ - switch (parametric_curve.function_type()) { - case Gfx::ICC::ParametricCurveTagData::FunctionType::Type0: - outln("{: >{}}Y = X**{}", "", indent_amount, parametric_curve.g()); - break; - case Gfx::ICC::ParametricCurveTagData::FunctionType::Type1: - outln("{: >{}}Y = ({}*X + {})**{} if X >= -{}/{}", "", indent_amount, - parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.b(), parametric_curve.a()); - outln("{: >{}}Y = 0 else", "", indent_amount); - break; - case Gfx::ICC::ParametricCurveTagData::FunctionType::Type2: - outln("{: >{}}Y = ({}*X + {})**{} + {} if X >= -{}/{}", "", indent_amount, - parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.c(), parametric_curve.b(), parametric_curve.a()); - outln("{: >{}}Y = {} else", "", indent_amount, parametric_curve.c()); - break; - case Gfx::ICC::ParametricCurveTagData::FunctionType::Type3: - outln("{: >{}}Y = ({}*X + {})**{} if X >= {}", "", indent_amount, - parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.d()); - outln("{: >{}}Y = {}*X else", "", indent_amount, parametric_curve.c()); - break; - case Gfx::ICC::ParametricCurveTagData::FunctionType::Type4: - outln("{: >{}}Y = ({}*X + {})**{} + {} if X >= {}", "", indent_amount, - parametric_curve.a(), parametric_curve.b(), parametric_curve.g(), parametric_curve.e(), parametric_curve.d()); - outln("{: >{}}Y = {}*X + {} else", "", indent_amount, parametric_curve.c(), parametric_curve.f()); - break; - } -} - -static float curve_distance_u8(Gfx::ICC::TagData const& tag1, Gfx::ICC::TagData const& tag2) -{ - VERIFY(tag1.type() == Gfx::ICC::CurveTagData::Type || tag1.type() == Gfx::ICC::ParametricCurveTagData::Type); - VERIFY(tag2.type() == Gfx::ICC::CurveTagData::Type || tag2.type() == Gfx::ICC::ParametricCurveTagData::Type); - - float curve1_data[256]; - if (tag1.type() == Gfx::ICC::CurveTagData::Type) { - auto& curve1 = static_cast(tag1); - for (int i = 0; i < 256; ++i) - curve1_data[i] = curve1.evaluate(i / 255.f); - } else { - auto& parametric_curve1 = static_cast(tag1); - for (int i = 0; i < 256; ++i) - curve1_data[i] = parametric_curve1.evaluate(i / 255.f); - } - - float curve2_data[256]; - if (tag2.type() == Gfx::ICC::CurveTagData::Type) { - auto& curve2 = static_cast(tag2); - for (int i = 0; i < 256; ++i) - curve2_data[i] = curve2.evaluate(i / 255.f); - } else { - auto& parametric_curve2 = static_cast(tag2); - for (int i = 0; i < 256; ++i) - curve2_data[i] = parametric_curve2.evaluate(i / 255.f); - } - - float distance = 0; - for (int i = 0; i < 256; ++i) - distance += fabsf(curve1_data[i] - curve2_data[i]); - return distance; -} - -static ErrorOr out_curve_tag(Gfx::ICC::TagData const& tag, int indent_amount) -{ - VERIFY(tag.type() == Gfx::ICC::CurveTagData::Type || tag.type() == Gfx::ICC::ParametricCurveTagData::Type); - if (tag.type() == Gfx::ICC::CurveTagData::Type) - out_curve(static_cast(tag), indent_amount); - if (tag.type() == Gfx::ICC::ParametricCurveTagData::Type) - out_parametric_curve(static_cast(tag), indent_amount); - - auto sRGB_curve = TRY(Gfx::ICC::sRGB_curve()); - - // Some example values (for abs distance summed over the 256 values of an u8): - // In Compact-ICC-Profiles/profiles: - // AdobeCompat-v2.icc: 1.14 (this is a gamma 2.2 curve, so not really sRGB but close) - // AdobeCompat-v4.icc: 1.13 - // AppleCompat-v2.icc: 11.94 (gamma 1.8 curve) - // DCI-P3-v4.icc: 8.29 (gamma 2.6 curve) - // DisplayP3-v2-magic.icc: 0.000912 (looks sRGB-ish) - // DisplayP3-v2-micro.icc: 0.010819 - // DisplayP3-v4.icc: 0.001062 (yes, definitely sRGB) - // Rec2020-g24-v4.icc: 4.119216 (gamma 2.4 curve) - // Rec2020-v4.icc: 7.805417 (custom non-sRGB curve) - // Rec709-v4.icc: 7.783267 (same custom non-sRGB curve as Rec2020) - // sRGB-v2-magic.icc: 0.000912 - // sRGB-v2-micro.icc: 0.010819 - // sRGB-v2-nano.icc: 0.052516 - // sRGB-v4.icc: 0.001062 - // scRGB-v2.icc: 48.379859 (linear identity curve) - // Google sRGB IEC61966-2.1 (from a Pixel jpeg, parametric): 0 - // Google sRGB IEC61966-2.1 (from a Pixel jpeg, LUT curve): 0.00096 - // Apple 2015 Display P3 (from iPhone 7, parametric): 0.011427 (has the old, left intersection for switching from linear to exponent) - // HP sRGB: 0.00096 - // color.org sRGB2014.icc: 0.00096 - // color.org sRGB_ICC_v4_Appearance.icc, AToB1Tag, a curves: 0.441926 -- but this is not _really_ sRGB - // color.org sRGB_v4_ICC_preference.icc, AToB1Tag, a curves: 2.205453 -- not really sRGB either - // So `< 0.06` identifies sRGB in practice (for u8 values). - float u8_distance_to_sRGB = curve_distance_u8(*sRGB_curve, tag); - if (u8_distance_to_sRGB < 0.06f) - outln("{: >{}}Looks like sRGB's curve (distance {})", "", indent_amount, u8_distance_to_sRGB); - else - outln("{: >{}}Does not look like sRGB's curve (distance: {})", "", indent_amount, u8_distance_to_sRGB); - - return {}; -} - -static ErrorOr out_curves(Vector const& curves) -{ - for (auto const& curve : curves) { - VERIFY(curve->type() == Gfx::ICC::CurveTagData::Type || curve->type() == Gfx::ICC::ParametricCurveTagData::Type); - outln(" type {}, relative offset {}, size {}", curve->type(), curve->offset(), curve->size()); - TRY(out_curve_tag(*curve, /*indent=*/12)); - } - return {}; -} - -static ErrorOr perform_debug_roundtrip(Gfx::ICC::Profile const& profile) -{ - size_t num_channels = Gfx::ICC::number_of_components_in_color_space(profile.data_color_space()); - Vector input, output; - input.resize(num_channels); - output.resize(num_channels); - - size_t const num_total_roundtrips = 500; - size_t num_lossless_roundtrips = 0; - - for (size_t i = 0; i < num_total_roundtrips; ++i) { - for (size_t j = 0; j < num_channels; ++j) - input[j] = get_random(); - auto color_in_profile_connection_space = TRY(profile.to_pcs(input)); - TRY(profile.from_pcs(profile, color_in_profile_connection_space, output)); - if (input != output) { - outln("roundtrip failed for {} -> {}", input, output); - } else { - ++num_lossless_roundtrips; - } - } - outln("lossless roundtrips: {} / {}", num_lossless_roundtrips, num_total_roundtrips); - return {}; -} - -static ErrorOr print_profile_measurement(Gfx::ICC::Profile const& profile) -{ - auto lab_from_rgb = [&profile](u8 r, u8 g, u8 b) { - u8 rgb[3] = { r, g, b }; - return profile.to_lab(rgb); - }; - float largest = -1, smallest = 1000; - Color largest_color1, largest_color2, smallest_color1, smallest_color2; - for (u8 r = 0; r < 254; ++r) { - out("\r{}/254", r + 1); - fflush(stdout); - for (u8 g = 0; g < 254; ++g) { - for (u8 b = 0; b < 254; ++b) { - auto lab = TRY(lab_from_rgb(r, g, b)); - u8 delta_r[] = { 1, 0, 0 }; - u8 delta_g[] = { 0, 1, 0 }; - u8 delta_b[] = { 0, 0, 1 }; - for (unsigned i = 0; i < sizeof(delta_r); ++i) { - auto lab2 = TRY(lab_from_rgb(r + delta_r[i], g + delta_g[i], b + delta_b[i])); - float delta = Gfx::DeltaE(lab, lab2); - if (delta > largest) { - largest = delta; - largest_color1 = Color(r, g, b); - largest_color2 = Color(r + delta_r[i], g + delta_g[i], b + delta_b[i]); - } - if (delta < smallest) { - smallest = delta; - smallest_color1 = Color(r, g, b); - smallest_color2 = Color(r + delta_r[i], g + delta_g[i], b + delta_b[i]); - } - } - } - } - } - outln("\rlargest difference between neighboring colors: {}, between {} and {}", largest, largest_color1, largest_color2); - outln("smallest difference between neighboring colors: {}, between {} and {}", smallest, smallest_color1, smallest_color2); - return {}; -} - -ErrorOr serenity_main(Main::Arguments arguments) -{ - Core::ArgsParser args_parser; - - StringView path; - args_parser.add_positional_argument(path, "Path to ICC profile or to image containing ICC profile", "FILE", Core::ArgsParser::Required::No); - - StringView name; - args_parser.add_option(name, "Name of a built-in profile, such as 'sRGB'", "name", 'n', "NAME"); - - StringView dump_out_path; - args_parser.add_option(dump_out_path, "Dump unmodified ICC profile bytes to this path", "dump-to", 0, "FILE"); - - StringView reencode_out_path; - args_parser.add_option(reencode_out_path, "Reencode ICC profile to this path", "reencode-to", 0, "FILE"); - - bool debug_roundtrip = false; - args_parser.add_option(debug_roundtrip, "Check how many u8 colors roundtrip losslessly through the profile. For debugging.", "debug-roundtrip"); - - bool measure = false; - args_parser.add_option(measure, "For RGB ICC profiles, print perceptually smallest and largest color step", "measure"); - - bool force_print = false; - args_parser.add_option(force_print, "Print profile even when writing ICC files", "print"); - - args_parser.parse(arguments); - - if (path.is_empty() && name.is_empty()) { - warnln("need either a path or a profile name"); - return 1; - } - if (!path.is_empty() && !name.is_empty()) { - warnln("can't have both a path and a profile name"); - return 1; - } - if (path.is_empty() && !dump_out_path.is_empty()) { - warnln("--dump-to only valid with path, not with profile name; use --reencode-to instead"); - return 1; - } - - ReadonlyBytes icc_bytes; - NonnullRefPtr profile = TRY([&]() -> ErrorOr> { - if (!name.is_empty()) { - if (name == "sRGB") - return Gfx::ICC::sRGB(); - return Error::from_string_literal("unknown profile name"); - } - auto file = TRY(Core::MappedFile::map(path)); - - auto decoder = TRY(Gfx::ImageDecoder::try_create_for_raw_bytes(file->bytes())); - if (decoder) { - if (auto embedded_icc_bytes = TRY(decoder->icc_data()); embedded_icc_bytes.has_value()) { - icc_bytes = *embedded_icc_bytes; - } else { - outln("image contains no embedded ICC profile"); - exit(1); - } - } else { - icc_bytes = file->bytes(); - } - - if (!dump_out_path.is_empty()) { - auto output_stream = TRY(Core::File::open(dump_out_path, Core::File::OpenMode::Write)); - TRY(output_stream->write_until_depleted(icc_bytes)); - } - return Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_bytes); - }()); - - if (!reencode_out_path.is_empty()) { - auto reencoded_bytes = TRY(Gfx::ICC::encode(profile)); - auto output_stream = TRY(Core::File::open(reencode_out_path, Core::File::OpenMode::Write)); - TRY(output_stream->write_until_depleted(reencoded_bytes)); - } - - if (debug_roundtrip) { - TRY(perform_debug_roundtrip(*profile)); - return 0; - } - - if (measure) { - if (profile->data_color_space() != Gfx::ICC::ColorSpace::RGB) { - warnln("--measure only works for RGB ICC profiles"); - return 1; - } - TRY(print_profile_measurement(*profile)); - } - - bool do_print = (dump_out_path.is_empty() && reencode_out_path.is_empty() && !measure) || force_print; - if (!do_print) - return 0; - - outln(" size: {} bytes", profile->on_disk_size()); - out_optional(" preferred CMM type", profile->preferred_cmm_type()); - outln(" version: {}", profile->version()); - outln(" device class: {}", Gfx::ICC::device_class_name(profile->device_class())); - outln(" data color space: {}", Gfx::ICC::data_color_space_name(profile->data_color_space())); - outln(" connection space: {}", Gfx::ICC::profile_connection_space_name(profile->connection_space())); - - if (auto time = profile->creation_timestamp().to_time_t(); !time.is_error()) { - // Print in friendly localtime for valid profiles. - outln("creation date and time: {}", Core::DateTime::from_timestamp(time.release_value())); - } else { - outln("creation date and time: {:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC (invalid)", - profile->creation_timestamp().year, profile->creation_timestamp().month, profile->creation_timestamp().day, - profile->creation_timestamp().hours, profile->creation_timestamp().minutes, profile->creation_timestamp().seconds); - } - - out_optional(" primary platform", profile->primary_platform().map([](auto platform) { return primary_platform_name(platform); })); - - auto flags = profile->flags(); - outln(" flags: {:#08x}", flags.bits()); - outln(" - {}embedded in file", flags.is_embedded_in_file() ? "" : "not "); - outln(" - can{} be used independently of embedded color data", flags.can_be_used_independently_of_embedded_color_data() ? "" : "not"); - if (auto unknown_icc_bits = flags.icc_bits() & ~Gfx::ICC::Flags::KnownBitsMask) - outln(" other unknown ICC bits: {:#04x}", unknown_icc_bits); - if (auto color_management_module_bits = flags.color_management_module_bits()) - outln(" CMM bits: {:#04x}", color_management_module_bits); - - out_optional(" device manufacturer", TRY(profile->device_manufacturer().map([](auto device_manufacturer) { - return hyperlink(device_manufacturer_url(device_manufacturer), device_manufacturer); - }))); - out_optional(" device model", TRY(profile->device_model().map([](auto device_model) { - return hyperlink(device_model_url(device_model), device_model); - }))); - - auto device_attributes = profile->device_attributes(); - outln(" device attributes: {:#016x}", device_attributes.bits()); - outln(" media is:"); - outln(" - {}", - device_attributes.media_reflectivity() == Gfx::ICC::DeviceAttributes::MediaReflectivity::Reflective ? "reflective" : "transparent"); - outln(" - {}", - device_attributes.media_glossiness() == Gfx::ICC::DeviceAttributes::MediaGlossiness::Glossy ? "glossy" : "matte"); - outln(" - {}", - device_attributes.media_polarity() == Gfx::ICC::DeviceAttributes::MediaPolarity::Positive ? "of positive polarity" : "of negative polarity"); - outln(" - {}", - device_attributes.media_color() == Gfx::ICC::DeviceAttributes::MediaColor::Colored ? "colored" : "black and white"); - VERIFY((flags.icc_bits() & ~Gfx::ICC::DeviceAttributes::KnownBitsMask) == 0); - if (auto vendor_bits = device_attributes.vendor_bits()) - outln(" vendor bits: {:#08x}", vendor_bits); - - outln(" rendering intent: {}", Gfx::ICC::rendering_intent_name(profile->rendering_intent())); - outln(" pcs illuminant: {}", profile->pcs_illuminant()); - out_optional(" creator", profile->creator()); - out_optional(" id", profile->id()); - - size_t profile_disk_size = icc_bytes.size(); - if (profile_disk_size != profile->on_disk_size()) { - VERIFY(profile_disk_size > profile->on_disk_size()); - outln("{} trailing bytes after profile data", profile_disk_size - profile->on_disk_size()); - } - - outln(""); - - outln("tags:"); - HashMap tag_data_to_first_signature; - TRY(profile->try_for_each_tag([&tag_data_to_first_signature](auto tag_signature, auto tag_data) -> ErrorOr { - if (auto name = tag_signature_spec_name(tag_signature); name.has_value()) - out("{} ({}): ", *name, tag_signature); - else - out("Unknown tag ({}): ", tag_signature); - outln("type {}, offset {}, size {}", tag_data->type(), tag_data->offset(), tag_data->size()); - - // Print tag data only the first time it's seen. - // (Different sigatures can refer to the same data.) - auto it = tag_data_to_first_signature.find(tag_data); - if (it != tag_data_to_first_signature.end()) { - outln(" (see {} above)", it->value); - return {}; - } - tag_data_to_first_signature.set(tag_data, tag_signature); - - if (tag_data->type() == Gfx::ICC::ChromaticityTagData::Type) { - auto& chromaticity = static_cast(*tag_data); - outln(" phosphor or colorant type: {}", Gfx::ICC::ChromaticityTagData::phosphor_or_colorant_type_name(chromaticity.phosphor_or_colorant_type())); - for (auto const& xy : chromaticity.xy_coordinates()) - outln(" x, y: {}, {}", xy.x, xy.y); - } else if (tag_data->type() == Gfx::ICC::CicpTagData::Type) { - auto& cicp = static_cast(*tag_data); - outln(" color primaries: {} - {}", cicp.color_primaries(), - Media::color_primaries_to_string((Media::ColorPrimaries)cicp.color_primaries())); - outln(" transfer characteristics: {} - {}", cicp.transfer_characteristics(), - Media::transfer_characteristics_to_string((Media::TransferCharacteristics)cicp.transfer_characteristics())); - outln(" matrix coefficients: {} - {}", cicp.matrix_coefficients(), - Media::matrix_coefficients_to_string((Media::MatrixCoefficients)cicp.matrix_coefficients())); - outln(" video full range flag: {} - {}", cicp.video_full_range_flag(), - Media::video_full_range_flag_to_string((Media::VideoFullRangeFlag)cicp.video_full_range_flag())); - } else if (tag_data->type() == Gfx::ICC::CurveTagData::Type) { - TRY(out_curve_tag(*tag_data, /*indent=*/4)); - } else if (tag_data->type() == Gfx::ICC::Lut16TagData::Type) { - auto& lut16 = static_cast(*tag_data); - outln(" input table: {} channels x {} entries", lut16.number_of_input_channels(), lut16.number_of_input_table_entries()); - outln(" output table: {} channels x {} entries", lut16.number_of_output_channels(), lut16.number_of_output_table_entries()); - outln(" color lookup table: {} grid points, {} total entries", lut16.number_of_clut_grid_points(), lut16.clut_values().size()); - - auto const& e = lut16.e_matrix(); - outln(" e = [ {}, {}, {},", e[0], e[1], e[2]); - outln(" {}, {}, {},", e[3], e[4], e[5]); - outln(" {}, {}, {} ]", e[6], e[7], e[8]); - } else if (tag_data->type() == Gfx::ICC::Lut8TagData::Type) { - auto& lut8 = static_cast(*tag_data); - outln(" input table: {} channels x {} entries", lut8.number_of_input_channels(), lut8.number_of_input_table_entries()); - outln(" output table: {} channels x {} entries", lut8.number_of_output_channels(), lut8.number_of_output_table_entries()); - outln(" color lookup table: {} grid points, {} total entries", lut8.number_of_clut_grid_points(), lut8.clut_values().size()); - - auto const& e = lut8.e_matrix(); - outln(" e = [ {}, {}, {},", e[0], e[1], e[2]); - outln(" {}, {}, {},", e[3], e[4], e[5]); - outln(" {}, {}, {} ]", e[6], e[7], e[8]); - } else if (tag_data->type() == Gfx::ICC::LutAToBTagData::Type) { - auto& a_to_b = static_cast(*tag_data); - outln(" {} input channels, {} output channels", a_to_b.number_of_input_channels(), a_to_b.number_of_output_channels()); - - if (auto const& optional_a_curves = a_to_b.a_curves(); optional_a_curves.has_value()) { - outln(" a curves: {} curves", optional_a_curves->size()); - TRY(out_curves(optional_a_curves.value())); - } else { - outln(" a curves: (not set)"); - } - - if (auto const& optional_clut = a_to_b.clut(); optional_clut.has_value()) { - auto const& clut = optional_clut.value(); - outln(" color lookup table: {} grid points, {}", - TRY(String::join(" x "sv, clut.number_of_grid_points_in_dimension)), - TRY(clut.values.visit( - [](Vector const& v) { return String::formatted("{} u8 entries", v.size()); }, - [](Vector const& v) { return String::formatted("{} u16 entries", v.size()); }))); - } else { - outln(" color lookup table: (not set)"); - } - - if (auto const& optional_m_curves = a_to_b.m_curves(); optional_m_curves.has_value()) { - outln(" m curves: {} curves", optional_m_curves->size()); - TRY(out_curves(optional_m_curves.value())); - } else { - outln(" m curves: (not set)"); - } - - if (auto const& optional_e = a_to_b.e_matrix(); optional_e.has_value()) { - auto const& e = optional_e.value(); - outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]); - outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]); - outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]); - } else { - outln(" e = (not set)"); - } - - outln(" b curves: {} curves", a_to_b.b_curves().size()); - TRY(out_curves(a_to_b.b_curves())); - } else if (tag_data->type() == Gfx::ICC::LutBToATagData::Type) { - auto& b_to_a = static_cast(*tag_data); - outln(" {} input channels, {} output channels", b_to_a.number_of_input_channels(), b_to_a.number_of_output_channels()); - - outln(" b curves: {} curves", b_to_a.b_curves().size()); - TRY(out_curves(b_to_a.b_curves())); - - if (auto const& optional_e = b_to_a.e_matrix(); optional_e.has_value()) { - auto const& e = optional_e.value(); - outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]); - outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]); - outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]); - } else { - outln(" e = (not set)"); - } - - if (auto const& optional_m_curves = b_to_a.m_curves(); optional_m_curves.has_value()) { - outln(" m curves: {} curves", optional_m_curves->size()); - TRY(out_curves(optional_m_curves.value())); - } else { - outln(" m curves: (not set)"); - } - - if (auto const& optional_clut = b_to_a.clut(); optional_clut.has_value()) { - auto const& clut = optional_clut.value(); - outln(" color lookup table: {} grid points, {}", - TRY(String::join(" x "sv, clut.number_of_grid_points_in_dimension)), - TRY(clut.values.visit( - [](Vector const& v) { return String::formatted("{} u8 entries", v.size()); }, - [](Vector const& v) { return String::formatted("{} u16 entries", v.size()); }))); - } else { - outln(" color lookup table: (not set)"); - } - - if (auto const& optional_a_curves = b_to_a.a_curves(); optional_a_curves.has_value()) { - outln(" a curves: {} curves", optional_a_curves->size()); - TRY(out_curves(optional_a_curves.value())); - } else { - outln(" a curves: (not set)"); - } - } else if (tag_data->type() == Gfx::ICC::MeasurementTagData::Type) { - auto& measurement = static_cast(*tag_data); - outln(" standard observer: {}", Gfx::ICC::MeasurementTagData::standard_observer_name(measurement.standard_observer())); - outln(" tristimulus value for measurement backing: {}", measurement.tristimulus_value_for_measurement_backing()); - outln(" measurement geometry: {}", Gfx::ICC::MeasurementTagData::measurement_geometry_name(measurement.measurement_geometry())); - outln(" measurement flare: {} %", measurement.measurement_flare() * 100); - outln(" standard illuminant: {}", Gfx::ICC::MeasurementTagData::standard_illuminant_name(measurement.standard_illuminant())); - } else if (tag_data->type() == Gfx::ICC::MultiLocalizedUnicodeTagData::Type) { - auto& multi_localized_unicode = static_cast(*tag_data); - for (auto& record : multi_localized_unicode.records()) { - outln(" {:c}{:c}/{:c}{:c}: \"{}\"", - record.iso_639_1_language_code >> 8, record.iso_639_1_language_code & 0xff, - record.iso_3166_1_country_code >> 8, record.iso_3166_1_country_code & 0xff, - record.text); - } - } else if (tag_data->type() == Gfx::ICC::NamedColor2TagData::Type) { - auto& named_colors = static_cast(*tag_data); - outln(" vendor specific flag: {:#08x}", named_colors.vendor_specific_flag()); - outln(" common name prefix: \"{}\"", named_colors.prefix()); - outln(" common name suffix: \"{}\"", named_colors.suffix()); - outln(" {} colors:", named_colors.size()); - for (size_t i = 0; i < min(named_colors.size(), 5u); ++i) { - auto const& pcs = named_colors.pcs_coordinates(i); - - // FIXME: Display decoded values? (See ICC v4 6.3.4.2 and 10.8.) - out(" \"{}\", PCS coordinates: {:#04x} {:#04x} {:#04x}", TRY(named_colors.color_name(i)), pcs.xyz.x, pcs.xyz.y, pcs.xyz.z); - if (auto number_of_device_coordinates = named_colors.number_of_device_coordinates(); number_of_device_coordinates > 0) { - out(", device coordinates:"); - for (size_t j = 0; j < number_of_device_coordinates; ++j) - out(" {:#04x}", named_colors.device_coordinates(i)[j]); - } - outln(); - } - if (named_colors.size() > 5u) - outln(" ..."); - } else if (tag_data->type() == Gfx::ICC::ParametricCurveTagData::Type) { - TRY(out_curve_tag(*tag_data, /*indent=*/4)); - } else if (tag_data->type() == Gfx::ICC::S15Fixed16ArrayTagData::Type) { - // This tag can contain arbitrarily many fixed-point numbers, but in practice it's - // exclusively used for the 'chad' tag, where it always contains 9 values that - // represent a 3x3 matrix. So print the values in groups of 3. - auto& fixed_array = static_cast(*tag_data); - out(" ["); - int i = 0; - for (auto value : fixed_array.values()) { - if (i > 0) { - out(","); - if (i % 3 == 0) { - outln(); - out(" "); - } - } - out(" {}", value); - i++; - } - outln(" ]"); - } else if (tag_data->type() == Gfx::ICC::SignatureTagData::Type) { - auto& signature = static_cast(*tag_data); - - if (auto name = signature.name_for_tag(tag_signature); name.has_value()) { - outln(" signature: {}", name.value()); - } else { - outln(" signature: Unknown ('{:c}{:c}{:c}{:c}' / {:#08x})", - signature.signature() >> 24, (signature.signature() >> 16) & 0xff, (signature.signature() >> 8) & 0xff, signature.signature() & 0xff, - signature.signature()); - } - } else if (tag_data->type() == Gfx::ICC::TextDescriptionTagData::Type) { - auto& text_description = static_cast(*tag_data); - outln(" ascii: \"{}\"", text_description.ascii_description()); - out_optional(" unicode", TRY(text_description.unicode_description().map([](auto description) { return String::formatted("\"{}\"", description); }))); - outln(" unicode language code: 0x{}", text_description.unicode_language_code()); - out_optional(" macintosh", TRY(text_description.macintosh_description().map([](auto description) { return String::formatted("\"{}\"", description); }))); - } else if (tag_data->type() == Gfx::ICC::TextTagData::Type) { - outln(" text: \"{}\"", static_cast(*tag_data).text()); - } else if (tag_data->type() == Gfx::ICC::ViewingConditionsTagData::Type) { - auto& viewing_conditions = static_cast(*tag_data); - outln(" unnormalized CIEXYZ values for illuminant (in which Y is in cd/m²): {}", viewing_conditions.unnormalized_ciexyz_values_for_illuminant()); - outln(" unnormalized CIEXYZ values for surround (in which Y is in cd/m²): {}", viewing_conditions.unnormalized_ciexyz_values_for_surround()); - outln(" illuminant type: {}", Gfx::ICC::MeasurementTagData::standard_illuminant_name(viewing_conditions.illuminant_type())); - } else if (tag_data->type() == Gfx::ICC::XYZTagData::Type) { - for (auto& xyz : static_cast(*tag_data).xyzs()) - outln(" {}", xyz); - } - return {}; - })); - - return 0; -} diff --git a/Utilities/image.cpp b/Utilities/image.cpp index a9039665211bf..71a224e3dbac2 100644 --- a/Utilities/image.cpp +++ b/Utilities/image.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -122,41 +121,6 @@ static ErrorOr strip_alpha(LoadedImage& image) return {}; } -static ErrorOr> convert_image_profile(LoadedImage& image, StringView convert_color_profile_path, OwnPtr maybe_source_icc_file) -{ - if (!image.icc_data.has_value()) - return Error::from_string_literal("No source color space embedded in image. Pass one with --assign-color-profile."); - - auto source_icc_file = move(maybe_source_icc_file); - auto source_icc_data = image.icc_data.value(); - auto icc_file = TRY(Core::MappedFile::map(convert_color_profile_path)); - image.icc_data = icc_file->bytes(); - - auto source_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(source_icc_data)); - auto destination_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_file->bytes())); - - if (destination_profile->data_color_space() != Gfx::ICC::ColorSpace::RGB) - return Error::from_string_literal("Can only convert to RGB at the moment, but destination color space is not RGB"); - - if (image.bitmap.has>()) { - if (source_profile->data_color_space() != Gfx::ICC::ColorSpace::CMYK) - return Error::from_string_literal("Source image data is CMYK but source color space is not CMYK"); - - auto& cmyk_frame = image.bitmap.get>(); - auto rgb_frame = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, cmyk_frame->size())); - TRY(destination_profile->convert_cmyk_image(*rgb_frame, *cmyk_frame, *source_profile)); - image.bitmap = RefPtr(move(rgb_frame)); - image.internal_format = Gfx::NaturalFrameFormat::RGB; - } else { - // FIXME: This likely wrong for grayscale images because they've been converted to - // RGB at this point, but their embedded color profile is still for grayscale. - auto& frame = image.bitmap.get>(); - TRY(destination_profile->convert_image(*frame, *source_profile)); - } - - return icc_file; -} - static ErrorOr save_image(LoadedImage& image, StringView out_path, u8 jpeg_quality, Optional webp_allowed_transforms) { auto stream = [out_path]() -> ErrorOr> { @@ -262,8 +226,6 @@ static ErrorOr parse_options(Main::Arguments arguments) args_parser.add_option(options.move_alpha_to_rgb, "Copy alpha channel to rgb, clear alpha", "move-alpha-to-rgb", {}); args_parser.add_option(options.strip_alpha, "Remove alpha channel", "strip-alpha", {}); args_parser.add_option(options.assign_color_profile_path, "Load color profile from file and assign it to output image", "assign-color-profile", {}, "FILE"); - args_parser.add_option(options.convert_color_profile_path, "Load color profile from file and convert output image from current profile to loaded profile", "convert-to-color-profile", {}, "FILE"); - args_parser.add_option(options.strip_color_profile, "Do not write color profile to output", "strip-color-profile", {}); args_parser.add_option(options.quality, "Quality used for the JPEG encoder, the default value is 75 on a scale from 0 to 100", "quality", {}, {}); StringView webp_allowed_transforms = "default"sv; args_parser.add_option(webp_allowed_transforms, "Comma-separated list of allowed transforms (predictor,p,color,c,subtract-green,sg,color-indexing,ci) for WebP output (default: all allowed)", "webp-allowed-transforms", {}, {}); @@ -309,9 +271,6 @@ ErrorOr serenity_main(Main::Arguments arguments) image.icc_data = icc_file->bytes(); } - if (!options.convert_color_profile_path.is_empty()) - icc_file = TRY(convert_image_profile(image, options.convert_color_profile_path, move(icc_file))); - if (options.strip_color_profile) image.icc_data.clear(); From 1e98fa96d741e0ff024aaa583a7d98f42690d287 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 21:22:16 +0100 Subject: [PATCH 190/237] LibWeb: Fix bogus `AesGcm` and `AesCtr` key import length validation The validation of the key size and specified algorithm was out of spec. It is now implemented correctly like in `AesCbc`. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 63fb0271454bb..620efd8786aeb 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -1573,12 +1573,15 @@ WebIDL::ExceptionOr> AesCtr::import_key(AlgorithmParams const // throw a DataError. auto data_bits = data.size() * 8; auto const& alg = jwk.alg; - if (data_bits == 128 && alg != "A128CTR") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); - } else if (data_bits == 192 && alg != "A192CTR") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); - } else if (data_bits == 256 && alg != "A256CTR") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); + if (data_bits == 128) { + if (alg.has_value() && alg != "A128CTR") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); + } else if (data_bits == 192) { + if (alg.has_value() && alg != "A192CTR") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); + } else if (data_bits == 256) { + if (alg.has_value() && alg != "A256CTR") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); } else { return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key size: {} bits", data_bits))); } @@ -1890,12 +1893,15 @@ WebIDL::ExceptionOr> AesGcm::import_key(AlgorithmParams const // throw a DataError. auto data_bits = data.size() * 8; auto const& alg = jwk.alg; - if (data_bits == 128 && alg != "A128GCM") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); - } else if (data_bits == 192 && alg != "A192GCM") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); - } else if (data_bits == 256 && alg != "A256GCM") { - return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); + if (data_bits == 128) { + if (alg.has_value() && alg != "A128GCM") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); + } else if (data_bits == 192) { + if (alg.has_value() && alg != "A192GCM") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); + } else if (data_bits == 256) { + if (alg.has_value() && alg != "A256GCM") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); } else { return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key size: {} bits", data_bits))); } From a65110ec0677eba8771cc8a5067ecfecc2cd529d Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 21:40:16 +0100 Subject: [PATCH 191/237] LibWeb: Define `HmacKeyAlgorithm` native accessor for `length` property The property was not accessible because it was not exposed to JS. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/KeyAlgorithms.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp b/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp index f62b727b8626e..999d6d73af3e7 100644 --- a/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp @@ -225,6 +225,7 @@ void HmacKeyAlgorithm::initialize(JS::Realm& realm) { Base::initialize(realm); define_native_accessor(realm, "hash", hash_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); + define_native_accessor(realm, "length", length_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); } void HmacKeyAlgorithm::visit_edges(JS::Cell::Visitor& visitor) From 6e33dbb533c3a4e12cb8a6fe785a3610378f9745 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Fri, 13 Dec 2024 22:34:12 +0100 Subject: [PATCH 192/237] LibWeb: Fix `ED25519` JWK key export format The presence of padding in the base64 fields and the typo made plenty of WPT tests fail. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 6 +-- .../okp_importKey_Ed25519.https.any.txt | 44 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 620efd8786aeb..6cce56120b631 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -4529,7 +4529,7 @@ WebIDL::ExceptionOr> ED25519::export_key(Bindings::KeyFormat // 4. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037]. if (key->type() == Bindings::KeyType::Public) { - jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(key_data)); + jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(key_data, AK::OmitPadding::Yes)); } else { // The "x" parameter of the "epk" field is set as follows: // Apply the appropriate ECDH function to the ephemeral private key (as scalar input) @@ -4537,13 +4537,13 @@ WebIDL::ExceptionOr> ED25519::export_key(Bindings::KeyFormat // The base64url encoding of the output is the value for the "x" parameter of the "epk" field. ::Crypto::Curves::Ed25519 curve; auto public_key = TRY_OR_THROW_OOM(vm, curve.generate_public_key(key_data)); - jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(key_data)); + jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key, AK::OmitPadding::Yes)); } // 5. If the [[type]] internal slot of key is "private" if (key->type() == Bindings::KeyType::Private) { // 1. Set the d attribute of jwk according to the definition in Section 2 of [RFC8037]. - jwk.d = TRY_OR_THROW_OOM(vm, encode_base64url(key_data)); + jwk.d = TRY_OR_THROW_OOM(vm, encode_base64url(key_data, AK::OmitPadding::Yes)); } // 6. Set the key_ops attribute of jwk to the usages attribute of key. diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt index 5ada1348762fc..f55036a7ff2aa 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt @@ -2,44 +2,44 @@ Harness status: OK Found 62 tests -32 Pass -30 Fail +52 Pass +10 Fail Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify]) Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify]) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify]) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, [verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, [verify]) Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, []) Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, []) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, []) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, []) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, []) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, []) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, []) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, []) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, []) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, []) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, []) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, []) Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify, verify]) Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify, verify]) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) -Fail Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) +Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, [verify, verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, [verify, verify]) Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign]) Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign]) -Fail Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) -Fail Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) +Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) +Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign, sign]) Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign, sign]) -Fail Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) -Fail Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) -Fail Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign]) +Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) +Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) +Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign]) Pass Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, false, [verify]) Pass Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, false, [verify]) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, false, [verify]) From 9613b87b12970c8af1664862a41cd652b32850f1 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 10:56:38 +0100 Subject: [PATCH 193/237] LibWeb: Fix `ED25519` PCKS#8 key export format The ASN1 structure for PCKS#8 was wrong and missing one wrapping of the key in a OctetString. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 6cce56120b631..578f7b604398a 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -4507,10 +4507,14 @@ WebIDL::ExceptionOr> ED25519::export_key(Bindings::KeyFormat // * Set the version field to 0. // * Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type with the following properties: // * Set the algorithm object identifier to the id-Ed25519 OID defined in [RFC8410]. - // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410], that represents the Ed25519 private key represented by the [[handle]] internal slot of key + // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, + // as defined in Section 7 of [RFC8410], that represents the Ed25519 private key + // represented by the [[handle]] internal slot of key + ::Crypto::ASN1::Encoder encoder; + TRY_OR_THROW_OOM(vm, encoder.write(key_data.bytes())); auto ed25519_oid = ::Crypto::ASN1::ed25519_oid; - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(key_data, ed25519_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(encoder.finish(), ed25519_oid, nullptr)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, move(data)); From 718b78dd43639d2e6709ea60b77b35a7ff5f0f69 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 11:46:32 +0100 Subject: [PATCH 194/237] LibWeb: Import WPT `RSA.importKey` tests This serves as a control for the changes in the next commits. I originally had a regression, but did not notice because the tests were missing. --- .../import_export/rsa_importKey.https.any.txt | 1062 +++++++++++++++++ .../rsa_importKey.https.any.html | 16 + .../import_export/rsa_importKey.https.any.js | 271 +++++ 3 files changed, 1349 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.js diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.txt new file mode 100644 index 0000000000000..2f73f5c850953 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.txt @@ -0,0 +1,1062 @@ +Harness status: OK + +Found 1056 tests + +480 Pass +576 Fail +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, true, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, true, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, true, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey, encrypt]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [wrapKey]) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-OAEP}, false, [encrypt, wrapKey, encrypt, wrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey, decrypt]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [unwrapKey]) +Pass Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-OAEP}, false, [decrypt, unwrapKey, decrypt, unwrapKey]) +Pass Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-OAEP}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSA-PSS}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSA-PSS}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSA-PSS}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (spki, buffer(162), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (pkcs8, buffer(636), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 1024 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 1024 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (spki, buffer(294), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (pkcs8, buffer(1218), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 2048 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 2048 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-1, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-256, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-384, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, true, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify]) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (spki, buffer(550), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [verify, verify]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (pkcs8, buffer(2376), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (pkcs8, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign]) +Fail Good parameters: 4096 bits (jwk, object(kty, n, e, d, p, q, dp, dq, qi), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, [sign, sign]) +Fail Empty Usages: 4096 bits (jwk, object(spki, pkcs8, jwk), {hash: SHA-512, name: RSASSA-PKCS1-v1_5}, false, []) \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.html new file mode 100644 index 0000000000000..d5b2ff3eb0709 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.html @@ -0,0 +1,16 @@ + + +WebCryptoAPI: importKey() for RSA keys + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.js new file mode 100644 index 0000000000000..c0917cab683ca --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/rsa_importKey.https.any.js @@ -0,0 +1,271 @@ +// META: title=WebCryptoAPI: importKey() for RSA keys +// META: timeout=long +// META: script=../util/helpers.js + +// Test importKey and exportKey for RSA algorithms. Only "happy paths" are +// currently tested - those where the operation should succeed. + + var subtle = crypto.subtle; + + var sizes = [1024, 2048, 4096]; + + var hashes = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; + + var keyData = { + 1024: { + spki: new Uint8Array([48, 129, 159, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 129, 141, 0, 48, 129, 137, 2, 129, 129, 0, 205, 153, 248, 177, 17, 159, 141, 10, 44, 231, 172, 139, 253, 12, 181, 71, 211, 72, 249, 49, 204, 156, 92, 167, 159, 222, 32, 229, 28, 64, 235, 1, 171, 38, 30, 1, 37, 61, 241, 232, 143, 113, 208, 134, 233, 75, 122, 190, 119, 131, 145, 3, 164, 118, 190, 224, 204, 135, 199, 67, 21, 26, 253, 68, 49, 250, 93, 143, 160, 81, 39, 28, 245, 78, 73, 207, 117, 0, 216, 169, 149, 126, 192, 155, 157, 67, 239, 112, 9, 140, 87, 241, 13, 3, 191, 211, 23, 72, 175, 86, 59, 136, 22, 135, 114, 13, 60, 123, 16, 161, 205, 85, 58, 199, 29, 41, 107, 110, 222, 236, 165, 185, 156, 138, 251, 54, 221, 151, 2, 3, 1, 0, 1]), + pkcs8: new Uint8Array([48, 130, 2, 120, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 4, 130, 2, 98, 48, 130, 2, 94, 2, 1, 0, 2, 129, 129, 0, 205, 153, 248, 177, 17, 159, 141, 10, 44, 231, 172, 139, 253, 12, 181, 71, 211, 72, 249, 49, 204, 156, 92, 167, 159, 222, 32, 229, 28, 64, 235, 1, 171, 38, 30, 1, 37, 61, 241, 232, 143, 113, 208, 134, 233, 75, 122, 190, 119, 131, 145, 3, 164, 118, 190, 224, 204, 135, 199, 67, 21, 26, 253, 68, 49, 250, 93, 143, 160, 81, 39, 28, 245, 78, 73, 207, 117, 0, 216, 169, 149, 126, 192, 155, 157, 67, 239, 112, 9, 140, 87, 241, 13, 3, 191, 211, 23, 72, 175, 86, 59, 136, 22, 135, 114, 13, 60, 123, 16, 161, 205, 85, 58, 199, 29, 41, 107, 110, 222, 236, 165, 185, 156, 138, 251, 54, 221, 151, 2, 3, 1, 0, 1, 2, 129, 128, 98, 162, 10, 252, 103, 71, 243, 145, 126, 25, 102, 93, 129, 248, 38, 191, 94, 77, 19, 191, 32, 57, 162, 249, 135, 104, 56, 191, 176, 222, 51, 223, 137, 11, 176, 57, 60, 116, 139, 40, 214, 39, 243, 177, 197, 25, 192, 184, 190, 253, 15, 4, 128, 81, 183, 32, 128, 254, 98, 73, 124, 70, 134, 88, 228, 85, 8, 229, 210, 6, 149, 141, 122, 147, 24, 166, 42, 57, 218, 125, 240, 230, 232, 249, 81, 145, 44, 6, 118, 237, 101, 205, 4, 181, 104, 85, 23, 96, 46, 169, 174, 213, 110, 34, 171, 89, 196, 20, 18, 1, 8, 241, 93, 32, 19, 144, 248, 183, 32, 96, 240, 101, 239, 247, 222, 249, 117, 1, 2, 65, 0, 244, 26, 192, 131, 146, 245, 205, 250, 134, 62, 229, 137, 14, 224, 194, 5, 127, 147, 154, 214, 93, 172, 226, 55, 98, 206, 25, 104, 223, 178, 48, 249, 83, 143, 5, 146, 16, 243, 180, 170, 119, 227, 17, 151, 48, 217, 88, 23, 30, 2, 73, 153, 181, 92, 163, 164, 241, 114, 66, 66, 152, 70, 42, 121, 2, 65, 0, 215, 158, 227, 12, 157, 88, 107, 153, 230, 66, 244, 207, 110, 18, 128, 60, 7, 140, 90, 136, 49, 11, 38, 144, 78, 64, 107, 167, 125, 41, 16, 167, 122, 152, 100, 129, 223, 206, 97, 170, 190, 1, 34, 79, 44, 221, 254, 204, 117, 122, 76, 249, 68, 169, 105, 152, 20, 161, 62, 40, 255, 101, 68, 143, 2, 65, 0, 169, 215, 127, 65, 76, 220, 104, 31, 186, 142, 66, 168, 213, 72, 62, 215, 18, 136, 2, 0, 203, 22, 194, 35, 37, 69, 31, 90, 223, 226, 28, 191, 45, 139, 98, 165, 217, 211, 167, 77, 192, 178, 166, 7, 155, 62, 110, 83, 79, 86, 234, 28, 223, 154, 128, 102, 0, 116, 174, 115, 165, 125, 148, 137, 2, 65, 0, 132, 212, 95, 192, 228, 169, 148, 215, 225, 46, 252, 75, 80, 222, 218, 218, 160, 55, 201, 137, 190, 212, 196, 179, 255, 80, 214, 64, 254, 236, 174, 82, 206, 70, 85, 28, 96, 248, 109, 216, 86, 102, 178, 113, 30, 13, 192, 42, 202, 112, 70, 61, 5, 28, 108, 109, 128, 191, 248, 96, 31, 61, 142, 103, 2, 65, 0, 205, 186, 73, 64, 8, 98, 158, 188, 82, 109, 82, 177, 5, 13, 132, 100, 97, 84, 15, 103, 183, 88, 37, 219, 0, 148, 88, 166, 79, 7, 85, 14, 64, 3, 157, 142, 132, 164, 226, 112, 236, 158, 218, 17, 7, 158, 184, 41, 20, 172, 194, 242, 44, 231, 78, 192, 134, 220, 83, 36, 191, 7, 35, 225]), + jwk: { + kty: "RSA", + n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc", + e: "AQAB", + d: "YqIK_GdH85F-GWZdgfgmv15NE78gOaL5h2g4v7DeM9-JC7A5PHSLKNYn87HFGcC4vv0PBIBRtyCA_mJJfEaGWORVCOXSBpWNepMYpio52n3w5uj5UZEsBnbtZc0EtWhVF2Auqa7VbiKrWcQUEgEI8V0gE5D4tyBg8GXv9975dQE", + p: "9BrAg5L1zfqGPuWJDuDCBX-TmtZdrOI3Ys4ZaN-yMPlTjwWSEPO0qnfjEZcw2VgXHgJJmbVco6TxckJCmEYqeQ", + q: "157jDJ1Ya5nmQvTPbhKAPAeMWogxCyaQTkBrp30pEKd6mGSB385hqr4BIk8s3f7MdXpM-USpaZgUoT4o_2VEjw", + dp: "qdd_QUzcaB-6jkKo1Ug-1xKIAgDLFsIjJUUfWt_iHL8ti2Kl2dOnTcCypgebPm5TT1bqHN-agGYAdK5zpX2UiQ", + dq: "hNRfwOSplNfhLvxLUN7a2qA3yYm-1MSz_1DWQP7srlLORlUcYPht2FZmsnEeDcAqynBGPQUcbG2Av_hgHz2OZw", + qi: "zbpJQAhinrxSbVKxBQ2EZGFUD2e3WCXbAJRYpk8HVQ5AA52OhKTicOye2hEHnrgpFKzC8iznTsCG3FMkvwcj4Q" + } + }, + + 2048: { + spki: new Uint8Array([48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 217, 133, 128, 235, 45, 23, 114, 244, 164, 118, 188, 84, 4, 190, 230, 13, 154, 60, 42, 203, 188, 242, 74, 116, 117, 77, 159, 90, 104, 18, 56, 143, 158, 63, 38, 10, 216, 22, 135, 221, 179, 102, 248, 218, 85, 148, 98, 179, 151, 241, 192, 151, 137, 109, 13, 246, 230, 222, 49, 192, 79, 141, 71, 205, 21, 96, 13, 17, 190, 78, 196, 230, 48, 158, 32, 4, 22, 37, 127, 171, 186, 139, 190, 211, 58, 176, 193, 101, 218, 60, 155, 31, 206, 194, 196, 233, 229, 42, 202, 99, 89, 167, 207, 84, 213, 39, 91, 68, 134, 191, 1, 162, 180, 95, 4, 250, 226, 11, 113, 125, 1, 167, 148, 87, 7, 40, 129, 82, 151, 178, 183, 242, 43, 224, 14, 243, 2, 56, 19, 202, 135, 183, 224, 190, 131, 67, 51, 92, 250, 240, 118, 158, 54, 108, 249, 37, 108, 244, 66, 57, 69, 139, 180, 126, 189, 107, 50, 240, 22, 137, 128, 103, 0, 146, 115, 247, 157, 69, 184, 91, 159, 51, 245, 115, 24, 223, 197, 175, 152, 26, 162, 150, 72, 52, 231, 245, 179, 48, 18, 211, 105, 100, 106, 103, 56, 178, 43, 202, 85, 229, 144, 102, 241, 230, 159, 106, 105, 241, 238, 222, 204, 232, 129, 183, 66, 63, 212, 77, 252, 122, 124, 152, 156, 66, 103, 65, 216, 129, 60, 63, 205, 192, 36, 181, 61, 132, 41, 10, 59, 237, 163, 200, 56, 114, 202, 253, 2, 3, 1, 0, 1]), + pkcs8: new Uint8Array([48, 130, 4, 190, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 4, 130, 4, 168, 48, 130, 4, 164, 2, 1, 0, 2, 130, 1, 1, 0, 217, 133, 128, 235, 45, 23, 114, 244, 164, 118, 188, 84, 4, 190, 230, 13, 154, 60, 42, 203, 188, 242, 74, 116, 117, 77, 159, 90, 104, 18, 56, 143, 158, 63, 38, 10, 216, 22, 135, 221, 179, 102, 248, 218, 85, 148, 98, 179, 151, 241, 192, 151, 137, 109, 13, 246, 230, 222, 49, 192, 79, 141, 71, 205, 21, 96, 13, 17, 190, 78, 196, 230, 48, 158, 32, 4, 22, 37, 127, 171, 186, 139, 190, 211, 58, 176, 193, 101, 218, 60, 155, 31, 206, 194, 196, 233, 229, 42, 202, 99, 89, 167, 207, 84, 213, 39, 91, 68, 134, 191, 1, 162, 180, 95, 4, 250, 226, 11, 113, 125, 1, 167, 148, 87, 7, 40, 129, 82, 151, 178, 183, 242, 43, 224, 14, 243, 2, 56, 19, 202, 135, 183, 224, 190, 131, 67, 51, 92, 250, 240, 118, 158, 54, 108, 249, 37, 108, 244, 66, 57, 69, 139, 180, 126, 189, 107, 50, 240, 22, 137, 128, 103, 0, 146, 115, 247, 157, 69, 184, 91, 159, 51, 245, 115, 24, 223, 197, 175, 152, 26, 162, 150, 72, 52, 231, 245, 179, 48, 18, 211, 105, 100, 106, 103, 56, 178, 43, 202, 85, 229, 144, 102, 241, 230, 159, 106, 105, 241, 238, 222, 204, 232, 129, 183, 66, 63, 212, 77, 252, 122, 124, 152, 156, 66, 103, 65, 216, 129, 60, 63, 205, 192, 36, 181, 61, 132, 41, 10, 59, 237, 163, 200, 56, 114, 202, 253, 2, 3, 1, 0, 1, 2, 130, 1, 0, 90, 210, 167, 117, 138, 170, 83, 209, 90, 42, 73, 144, 59, 59, 10, 11, 123, 238, 203, 95, 174, 80, 236, 77, 155, 253, 1, 32, 90, 123, 225, 41, 246, 69, 31, 185, 63, 104, 136, 234, 68, 210, 37, 237, 227, 245, 197, 16, 127, 204, 237, 65, 88, 156, 52, 76, 119, 49, 39, 76, 200, 234, 144, 164, 76, 220, 130, 24, 122, 129, 161, 45, 11, 247, 186, 30, 122, 176, 197, 146, 10, 157, 246, 219, 115, 146, 1, 238, 105, 37, 13, 16, 70, 224, 132, 31, 181, 20, 28, 213, 70, 198, 14, 135, 185, 72, 105, 143, 63, 67, 217, 134, 250, 17, 2, 159, 78, 106, 192, 196, 21, 64, 199, 107, 95, 13, 198, 144, 212, 69, 255, 226, 191, 121, 46, 30, 103, 153, 111, 171, 166, 137, 88, 229, 86, 142, 66, 238, 136, 24, 72, 248, 27, 43, 116, 101, 215, 99, 39, 246, 212, 111, 241, 132, 169, 7, 252, 19, 104, 172, 233, 8, 40, 227, 172, 42, 47, 36, 134, 34, 214, 97, 228, 179, 215, 193, 4, 222, 129, 165, 1, 59, 216, 171, 50, 17, 100, 68, 199, 226, 114, 175, 49, 6, 95, 129, 122, 189, 198, 152, 17, 113, 70, 121, 104, 51, 75, 18, 210, 27, 237, 93, 87, 104, 49, 64, 112, 122, 198, 34, 61, 209, 7, 6, 121, 22, 191, 95, 151, 248, 124, 7, 87, 143, 45, 123, 22, 128, 153, 197, 130, 196, 244, 164, 225, 241, 2, 129, 129, 0, 252, 223, 109, 18, 211, 223, 124, 146, 67, 138, 211, 142, 156, 153, 102, 192, 192, 236, 129, 21, 14, 158, 28, 228, 12, 184, 69, 239, 165, 195, 209, 9, 236, 240, 88, 59, 143, 104, 199, 197, 124, 83, 168, 201, 166, 249, 158, 156, 67, 158, 15, 116, 155, 224, 83, 172, 112, 187, 1, 225, 127, 254, 175, 175, 214, 214, 36, 111, 218, 85, 109, 33, 228, 157, 192, 61, 195, 207, 25, 136, 154, 244, 134, 69, 18, 103, 225, 172, 131, 16, 168, 70, 3, 30, 5, 98, 162, 47, 88, 191, 99, 241, 127, 93, 36, 4, 72, 97, 227, 7, 70, 60, 141, 25, 150, 77, 170, 201, 86, 129, 29, 96, 60, 41, 231, 190, 200, 107, 2, 129, 129, 0, 220, 54, 40, 140, 204, 79, 7, 149, 241, 40, 229, 237, 13, 3, 118, 172, 76, 61, 137, 8, 253, 72, 223, 119, 189, 19, 87, 199, 3, 61, 197, 45, 111, 18, 58, 224, 121, 190, 144, 46, 143, 225, 7, 129, 10, 154, 24, 140, 96, 246, 212, 224, 232, 144, 67, 98, 6, 188, 167, 17, 224, 215, 160, 182, 249, 132, 174, 249, 21, 78, 138, 59, 186, 184, 239, 10, 71, 146, 46, 189, 206, 165, 57, 50, 38, 241, 230, 57, 169, 77, 76, 229, 53, 45, 184, 87, 22, 194, 94, 48, 68, 246, 171, 255, 73, 197, 25, 64, 13, 132, 56, 120, 241, 100, 197, 243, 171, 84, 246, 32, 86, 55, 55, 216, 121, 64, 52, 55, 2, 129, 128, 109, 221, 189, 12, 35, 21, 196, 143, 223, 220, 159, 82, 36, 227, 217, 107, 1, 231, 63, 166, 32, 117, 189, 227, 175, 75, 24, 199, 168, 99, 205, 156, 220, 95, 8, 86, 200, 86, 36, 5, 191, 160, 177, 130, 251, 147, 20, 192, 155, 248, 62, 138, 209, 118, 195, 163, 246, 78, 169, 224, 137, 181, 228, 43, 39, 210, 94, 126, 98, 132, 31, 40, 76, 165, 229, 114, 112, 114, 184, 139, 75, 151, 214, 6, 136, 154, 173, 200, 64, 33, 170, 154, 208, 155, 232, 135, 20, 36, 50, 16, 229, 161, 117, 78, 200, 105, 59, 241, 155, 171, 251, 110, 47, 119, 224, 127, 218, 38, 35, 249, 113, 3, 240, 223, 220, 26, 94, 5, 2, 129, 129, 0, 149, 113, 187, 187, 49, 188, 64, 109, 165, 168, 23, 193, 244, 30, 241, 158, 164, 110, 238, 92, 199, 103, 121, 32, 141, 148, 94, 241, 148, 101, 139, 54, 246, 53, 236, 247, 2, 40, 45, 57, 44, 51, 143, 32, 39, 205, 195, 243, 32, 170, 226, 117, 111, 222, 215, 155, 226, 238, 140, 131, 57, 143, 156, 102, 16, 151, 215, 22, 251, 58, 189, 221, 35, 46, 246, 42, 135, 191, 209, 48, 198, 216, 162, 36, 67, 1, 207, 56, 58, 137, 87, 50, 6, 16, 237, 21, 77, 64, 195, 35, 6, 234, 80, 119, 131, 220, 218, 241, 249, 58, 78, 8, 229, 233, 121, 221, 143, 220, 172, 219, 237, 38, 180, 35, 152, 197, 213, 169, 2, 129, 129, 0, 157, 34, 27, 203, 101, 161, 91, 231, 149, 223, 255, 186, 178, 175, 168, 93, 194, 163, 171, 101, 186, 95, 110, 38, 250, 23, 38, 18, 213, 87, 33, 41, 187, 18, 0, 21, 202, 68, 70, 236, 63, 219, 158, 201, 128, 166, 97, 210, 170, 210, 56, 80, 81, 24, 152, 240, 124, 20, 135, 22, 9, 92, 209, 189, 96, 214, 49, 70, 74, 200, 155, 82, 70, 96, 189, 70, 89, 82, 210, 229, 125, 135, 64, 183, 195, 243, 219, 121, 73, 43, 22, 184, 122, 92, 209, 118, 126, 19, 82, 110, 246, 109, 121, 198, 145, 226, 199, 242, 82, 139, 105, 101, 44, 41, 186, 33, 10, 94, 103, 157, 35, 178, 26, 104, 12, 191, 13, 7]), + jwk: { + kty: "RSA", + n: "2YWA6y0XcvSkdrxUBL7mDZo8Ksu88kp0dU2fWmgSOI-ePyYK2BaH3bNm-NpVlGKzl_HAl4ltDfbm3jHAT41HzRVgDRG-TsTmMJ4gBBYlf6u6i77TOrDBZdo8mx_OwsTp5SrKY1mnz1TVJ1tEhr8BorRfBPriC3F9AaeUVwcogVKXsrfyK-AO8wI4E8qHt-C-g0MzXPrwdp42bPklbPRCOUWLtH69azLwFomAZwCSc_edRbhbnzP1cxjfxa-YGqKWSDTn9bMwEtNpZGpnOLIrylXlkGbx5p9qafHu3szogbdCP9RN_Hp8mJxCZ0HYgTw_zcAktT2EKQo77aPIOHLK_Q", + e: "AQAB", + d: "WtKndYqqU9FaKkmQOzsKC3vuy1-uUOxNm_0BIFp74Sn2RR-5P2iI6kTSJe3j9cUQf8ztQVicNEx3MSdMyOqQpEzcghh6gaEtC_e6HnqwxZIKnfbbc5IB7mklDRBG4IQftRQc1UbGDoe5SGmPP0PZhvoRAp9OasDEFUDHa18NxpDURf_iv3kuHmeZb6umiVjlVo5C7ogYSPgbK3Rl12Mn9tRv8YSpB_wTaKzpCCjjrCovJIYi1mHks9fBBN6BpQE72KsyEWREx-JyrzEGX4F6vcaYEXFGeWgzSxLSG-1dV2gxQHB6xiI90QcGeRa_X5f4fAdXjy17FoCZxYLE9KTh8Q", + p: "_N9tEtPffJJDitOOnJlmwMDsgRUOnhzkDLhF76XD0Qns8Fg7j2jHxXxTqMmm-Z6cQ54PdJvgU6xwuwHhf_6vr9bWJG_aVW0h5J3APcPPGYia9IZFEmfhrIMQqEYDHgVioi9Yv2Pxf10kBEhh4wdGPI0Zlk2qyVaBHWA8Kee-yGs", + q: "3DYojMxPB5XxKOXtDQN2rEw9iQj9SN93vRNXxwM9xS1vEjrgeb6QLo_hB4EKmhiMYPbU4OiQQ2IGvKcR4NegtvmErvkVToo7urjvCkeSLr3OpTkyJvHmOalNTOU1LbhXFsJeMET2q_9JxRlADYQ4ePFkxfOrVPYgVjc32HlANDc", + dp: "bd29DCMVxI_f3J9SJOPZawHnP6Ygdb3jr0sYx6hjzZzcXwhWyFYkBb-gsYL7kxTAm_g-itF2w6P2TqngibXkKyfSXn5ihB8oTKXlcnByuItLl9YGiJqtyEAhqprQm-iHFCQyEOWhdU7IaTvxm6v7bi934H_aJiP5cQPw39waXgU", + dq: "lXG7uzG8QG2lqBfB9B7xnqRu7lzHZ3kgjZRe8ZRlizb2Nez3AigtOSwzjyAnzcPzIKridW_e15vi7oyDOY-cZhCX1xb7Or3dIy72Koe_0TDG2KIkQwHPODqJVzIGEO0VTUDDIwbqUHeD3Nrx-TpOCOXped2P3Kzb7Sa0I5jF1ak", + qi: "nSIby2WhW-eV3_-6sq-oXcKjq2W6X24m-hcmEtVXISm7EgAVykRG7D_bnsmApmHSqtI4UFEYmPB8FIcWCVzRvWDWMUZKyJtSRmC9RllS0uV9h0C3w_PbeUkrFrh6XNF2fhNSbvZtecaR4sfyUotpZSwpuiEKXmedI7IaaAy_DQc" + } + }, + + 4096: { + spki: new Uint8Array([48, 130, 2, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 2, 15, 0, 48, 130, 2, 10, 2, 130, 2, 1, 0, 218, 170, 246, 76, 189, 156, 216, 153, 155, 176, 221, 14, 44, 132, 103, 104, 0, 127, 100, 166, 245, 248, 104, 125, 31, 74, 155, 226, 90, 193, 184, 54, 170, 145, 111, 222, 20, 252, 19, 248, 146, 44, 190, 115, 73, 188, 52, 251, 4, 178, 121, 238, 212, 204, 34, 62, 122, 100, 203, 111, 233, 231, 210, 73, 53, 146, 147, 211, 14, 161, 109, 137, 212, 175, 226, 18, 183, 173, 103, 103, 30, 128, 31, 218, 69, 126, 234, 65, 88, 231, 160, 91, 51, 245, 77, 54, 4, 167, 192, 33, 68, 244, 163, 242, 187, 111, 209, 180, 241, 221, 107, 172, 5, 40, 134, 47, 210, 85, 8, 112, 57, 186, 29, 131, 176, 93, 116, 198, 202, 82, 108, 251, 209, 3, 72, 75, 143, 59, 44, 222, 56, 89, 69, 103, 159, 211, 160, 19, 214, 173, 77, 133, 0, 68, 219, 164, 79, 64, 238, 65, 189, 201, 248, 173, 180, 146, 196, 238, 86, 232, 215, 109, 39, 165, 162, 16, 230, 46, 134, 234, 148, 106, 34, 230, 198, 63, 231, 143, 16, 179, 208, 109, 22, 100, 54, 156, 107, 132, 28, 208, 118, 205, 217, 89, 228, 75, 196, 169, 181, 5, 85, 157, 144, 110, 129, 186, 141, 119, 104, 162, 206, 170, 115, 7, 96, 82, 240, 33, 143, 81, 243, 215, 67, 96, 137, 207, 209, 22, 162, 251, 108, 208, 232, 32, 236, 205, 167, 174, 161, 116, 13, 249, 187, 22, 240, 185, 172, 160, 103, 94, 162, 147, 26, 15, 143, 183, 147, 98, 231, 117, 134, 185, 50, 64, 40, 30, 27, 13, 152, 132, 40, 138, 32, 78, 158, 162, 207, 212, 229, 210, 251, 88, 116, 67, 229, 164, 164, 147, 59, 32, 94, 217, 197, 242, 149, 102, 74, 219, 46, 127, 68, 28, 116, 10, 2, 249, 231, 130, 123, 29, 45, 73, 56, 17, 195, 208, 45, 25, 60, 252, 98, 189, 109, 25, 0, 253, 151, 254, 124, 211, 48, 23, 156, 78, 163, 154, 188, 17, 69, 14, 188, 16, 64, 59, 190, 136, 70, 162, 253, 237, 156, 111, 41, 27, 40, 63, 205, 204, 94, 0, 50, 237, 62, 87, 211, 115, 91, 68, 194, 104, 119, 72, 106, 226, 160, 48, 165, 138, 134, 2, 138, 153, 181, 38, 249, 48, 120, 72, 15, 245, 227, 15, 164, 64, 188, 74, 4, 84, 213, 83, 67, 73, 87, 181, 72, 94, 46, 54, 193, 252, 188, 14, 207, 28, 82, 159, 131, 168, 238, 168, 145, 28, 230, 27, 126, 151, 93, 5, 96, 68, 126, 66, 174, 155, 101, 123, 20, 218, 131, 92, 124, 78, 82, 44, 55, 139, 77, 105, 177, 136, 121, 177, 43, 77, 12, 240, 0, 76, 20, 133, 121, 129, 73, 15, 160, 200, 150, 114, 95, 59, 59, 165, 240, 204, 13, 156, 134, 194, 4, 70, 158, 213, 111, 229, 103, 216, 239, 132, 16, 184, 151, 206, 254, 229, 62, 23, 58, 125, 49, 144, 208, 215, 2, 3, 1, 0, 1]), + pkcs8: new Uint8Array([48, 130, 9, 68, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 4, 130, 9, 46, 48, 130, 9, 42, 2, 1, 0, 2, 130, 2, 1, 0, 218, 170, 246, 76, 189, 156, 216, 153, 155, 176, 221, 14, 44, 132, 103, 104, 0, 127, 100, 166, 245, 248, 104, 125, 31, 74, 155, 226, 90, 193, 184, 54, 170, 145, 111, 222, 20, 252, 19, 248, 146, 44, 190, 115, 73, 188, 52, 251, 4, 178, 121, 238, 212, 204, 34, 62, 122, 100, 203, 111, 233, 231, 210, 73, 53, 146, 147, 211, 14, 161, 109, 137, 212, 175, 226, 18, 183, 173, 103, 103, 30, 128, 31, 218, 69, 126, 234, 65, 88, 231, 160, 91, 51, 245, 77, 54, 4, 167, 192, 33, 68, 244, 163, 242, 187, 111, 209, 180, 241, 221, 107, 172, 5, 40, 134, 47, 210, 85, 8, 112, 57, 186, 29, 131, 176, 93, 116, 198, 202, 82, 108, 251, 209, 3, 72, 75, 143, 59, 44, 222, 56, 89, 69, 103, 159, 211, 160, 19, 214, 173, 77, 133, 0, 68, 219, 164, 79, 64, 238, 65, 189, 201, 248, 173, 180, 146, 196, 238, 86, 232, 215, 109, 39, 165, 162, 16, 230, 46, 134, 234, 148, 106, 34, 230, 198, 63, 231, 143, 16, 179, 208, 109, 22, 100, 54, 156, 107, 132, 28, 208, 118, 205, 217, 89, 228, 75, 196, 169, 181, 5, 85, 157, 144, 110, 129, 186, 141, 119, 104, 162, 206, 170, 115, 7, 96, 82, 240, 33, 143, 81, 243, 215, 67, 96, 137, 207, 209, 22, 162, 251, 108, 208, 232, 32, 236, 205, 167, 174, 161, 116, 13, 249, 187, 22, 240, 185, 172, 160, 103, 94, 162, 147, 26, 15, 143, 183, 147, 98, 231, 117, 134, 185, 50, 64, 40, 30, 27, 13, 152, 132, 40, 138, 32, 78, 158, 162, 207, 212, 229, 210, 251, 88, 116, 67, 229, 164, 164, 147, 59, 32, 94, 217, 197, 242, 149, 102, 74, 219, 46, 127, 68, 28, 116, 10, 2, 249, 231, 130, 123, 29, 45, 73, 56, 17, 195, 208, 45, 25, 60, 252, 98, 189, 109, 25, 0, 253, 151, 254, 124, 211, 48, 23, 156, 78, 163, 154, 188, 17, 69, 14, 188, 16, 64, 59, 190, 136, 70, 162, 253, 237, 156, 111, 41, 27, 40, 63, 205, 204, 94, 0, 50, 237, 62, 87, 211, 115, 91, 68, 194, 104, 119, 72, 106, 226, 160, 48, 165, 138, 134, 2, 138, 153, 181, 38, 249, 48, 120, 72, 15, 245, 227, 15, 164, 64, 188, 74, 4, 84, 213, 83, 67, 73, 87, 181, 72, 94, 46, 54, 193, 252, 188, 14, 207, 28, 82, 159, 131, 168, 238, 168, 145, 28, 230, 27, 126, 151, 93, 5, 96, 68, 126, 66, 174, 155, 101, 123, 20, 218, 131, 92, 124, 78, 82, 44, 55, 139, 77, 105, 177, 136, 121, 177, 43, 77, 12, 240, 0, 76, 20, 133, 121, 129, 73, 15, 160, 200, 150, 114, 95, 59, 59, 165, 240, 204, 13, 156, 134, 194, 4, 70, 158, 213, 111, 229, 103, 216, 239, 132, 16, 184, 151, 206, 254, 229, 62, 23, 58, 125, 49, 144, 208, 215, 2, 3, 1, 0, 1, 2, 130, 2, 1, 0, 185, 115, 209, 92, 24, 92, 19, 159, 131, 89, 166, 193, 68, 164, 46, 135, 24, 20, 243, 42, 94, 230, 4, 200, 73, 103, 159, 121, 131, 251, 83, 222, 153, 30, 171, 191, 176, 16, 114, 103, 152, 161, 118, 12, 148, 246, 152, 0, 100, 101, 113, 224, 74, 125, 174, 117, 74, 156, 125, 165, 54, 189, 179, 172, 255, 80, 135, 42, 178, 247, 217, 204, 209, 163, 49, 155, 42, 72, 88, 176, 46, 63, 255, 195, 192, 184, 248, 183, 223, 76, 226, 197, 54, 245, 206, 60, 8, 10, 181, 122, 1, 223, 113, 196, 133, 143, 58, 77, 185, 235, 78, 76, 32, 59, 212, 66, 110, 162, 75, 123, 210, 153, 180, 58, 97, 179, 129, 60, 175, 142, 228, 123, 85, 50, 241, 119, 147, 204, 94, 43, 65, 163, 4, 167, 243, 247, 41, 134, 105, 197, 165, 63, 45, 145, 56, 174, 203, 192, 135, 209, 29, 195, 83, 179, 14, 184, 131, 104, 152, 48, 245, 179, 207, 178, 60, 23, 21, 1, 84, 207, 82, 124, 9, 137, 171, 141, 187, 55, 172, 180, 180, 10, 48, 185, 97, 79, 156, 39, 249, 192, 27, 98, 77, 250, 93, 18, 157, 130, 72, 210, 115, 96, 36, 132, 116, 101, 225, 96, 234, 79, 89, 243, 89, 135, 97, 252, 53, 72, 97, 34, 226, 41, 41, 45, 144, 243, 189, 162, 243, 43, 69, 136, 143, 182, 140, 223, 134, 93, 38, 245, 36, 125, 46, 93, 48, 94, 215, 39, 156, 57, 86, 93, 207, 204, 72, 106, 112, 215, 203, 230, 80, 20, 137, 224, 242, 33, 146, 33, 108, 188, 185, 254, 117, 189, 240, 82, 64, 60, 186, 247, 190, 138, 170, 159, 147, 75, 49, 148, 101, 174, 130, 21, 177, 211, 121, 6, 153, 144, 230, 166, 181, 155, 94, 232, 2, 4, 119, 236, 35, 133, 253, 223, 14, 30, 199, 57, 215, 31, 251, 90, 167, 19, 231, 154, 54, 225, 85, 68, 17, 234, 158, 53, 50, 243, 182, 149, 193, 214, 60, 188, 6, 38, 2, 200, 161, 232, 193, 30, 153, 231, 221, 57, 140, 55, 69, 35, 21, 153, 34, 238, 175, 65, 253, 210, 119, 125, 120, 116, 153, 127, 67, 204, 9, 66, 210, 200, 165, 212, 216, 2, 62, 19, 15, 171, 77, 183, 247, 127, 224, 138, 41, 208, 170, 227, 36, 158, 176, 111, 128, 172, 70, 73, 241, 148, 172, 50, 174, 126, 80, 177, 235, 93, 89, 102, 84, 76, 221, 30, 216, 49, 125, 142, 35, 45, 96, 224, 60, 161, 63, 48, 85, 143, 20, 76, 182, 111, 15, 156, 139, 55, 155, 113, 226, 248, 239, 130, 252, 241, 197, 247, 124, 61, 39, 197, 170, 119, 76, 136, 195, 180, 169, 106, 240, 234, 101, 114, 207, 11, 160, 170, 139, 194, 187, 48, 22, 114, 84, 64, 151, 30, 212, 99, 213, 176, 106, 79, 232, 127, 197, 153, 133, 8, 56, 210, 83, 67, 106, 124, 231, 96, 2, 145, 2, 130, 1, 1, 0, 244, 218, 215, 194, 174, 36, 99, 217, 1, 4, 236, 11, 160, 86, 85, 65, 206, 36, 36, 143, 205, 108, 166, 191, 91, 209, 75, 117, 7, 81, 33, 179, 44, 101, 145, 215, 39, 117, 195, 81, 31, 111, 36, 7, 26, 105, 30, 249, 91, 2, 2, 237, 126, 141, 231, 153, 213, 181, 100, 234, 219, 192, 114, 179, 215, 229, 39, 212, 107, 9, 55, 220, 136, 233, 237, 28, 74, 97, 6, 22, 26, 47, 150, 83, 82, 95, 186, 146, 22, 38, 176, 231, 255, 166, 199, 223, 217, 86, 142, 56, 43, 199, 25, 247, 249, 122, 59, 142, 152, 20, 49, 147, 13, 132, 249, 203, 251, 146, 116, 96, 88, 81, 232, 45, 106, 100, 187, 99, 73, 32, 203, 134, 30, 223, 100, 179, 179, 128, 81, 242, 25, 85, 137, 125, 96, 153, 240, 224, 86, 20, 206, 24, 26, 197, 233, 164, 158, 50, 222, 103, 197, 211, 144, 101, 182, 205, 201, 51, 23, 231, 125, 229, 130, 61, 139, 204, 195, 243, 69, 38, 185, 187, 48, 249, 140, 107, 137, 39, 234, 21, 13, 43, 24, 112, 108, 109, 15, 25, 57, 55, 127, 40, 152, 238, 227, 96, 86, 157, 114, 35, 52, 54, 38, 140, 85, 42, 119, 53, 99, 35, 133, 208, 240, 65, 171, 8, 71, 255, 243, 248, 176, 166, 17, 178, 92, 62, 203, 56, 158, 31, 169, 223, 123, 7, 118, 216, 166, 132, 83, 62, 112, 160, 99, 244, 132, 29, 2, 130, 1, 1, 0, 228, 158, 249, 243, 243, 94, 42, 189, 87, 61, 152, 139, 197, 122, 33, 97, 4, 39, 135, 66, 219, 225, 11, 70, 103, 92, 115, 10, 8, 225, 5, 2, 220, 32, 23, 147, 56, 111, 237, 98, 48, 174, 122, 207, 109, 152, 187, 125, 220, 186, 73, 127, 42, 82, 39, 228, 163, 12, 188, 36, 71, 107, 52, 235, 223, 200, 7, 38, 6, 167, 28, 158, 26, 213, 126, 186, 90, 152, 133, 44, 53, 156, 61, 130, 92, 163, 3, 27, 35, 185, 141, 112, 236, 246, 210, 107, 75, 245, 33, 126, 134, 215, 41, 1, 244, 220, 36, 93, 22, 232, 50, 62, 68, 141, 153, 118, 62, 1, 167, 197, 202, 113, 187, 196, 186, 251, 161, 128, 66, 211, 145, 103, 133, 69, 207, 155, 117, 65, 76, 251, 125, 43, 224, 105, 171, 6, 29, 254, 31, 111, 144, 5, 158, 166, 180, 143, 163, 205, 212, 151, 7, 11, 50, 234, 82, 37, 143, 75, 104, 124, 97, 69, 220, 246, 202, 45, 25, 40, 220, 23, 92, 116, 112, 114, 204, 198, 140, 48, 111, 191, 53, 28, 9, 134, 234, 90, 168, 243, 108, 75, 197, 99, 162, 173, 31, 194, 97, 224, 184, 76, 227, 170, 199, 106, 129, 14, 77, 234, 231, 38, 192, 197, 233, 174, 150, 240, 55, 252, 241, 27, 97, 169, 49, 49, 115, 9, 218, 65, 253, 14, 253, 217, 91, 141, 44, 68, 32, 247, 219, 199, 31, 45, 212, 68, 46, 131, 2, 130, 1, 1, 0, 225, 142, 199, 187, 155, 88, 2, 114, 225, 49, 123, 144, 170, 63, 93, 130, 165, 55, 62, 71, 10, 97, 208, 169, 239, 23, 58, 127, 176, 33, 216, 253, 137, 36, 119, 216, 207, 140, 248, 68, 62, 196, 207, 87, 139, 200, 210, 179, 186, 86, 124, 3, 243, 213, 29, 72, 229, 73, 152, 145, 145, 166, 19, 4, 1, 26, 36, 58, 213, 239, 67, 250, 112, 85, 174, 11, 165, 169, 3, 70, 81, 17, 13, 85, 236, 72, 43, 66, 112, 13, 108, 98, 11, 107, 196, 44, 61, 182, 50, 133, 36, 46, 225, 137, 65, 212, 140, 16, 171, 159, 206, 155, 60, 149, 6, 216, 22, 3, 176, 25, 32, 195, 51, 50, 195, 19, 208, 91, 129, 254, 39, 254, 129, 106, 33, 6, 57, 145, 55, 235, 225, 210, 158, 57, 85, 71, 250, 81, 110, 122, 243, 239, 216, 154, 0, 197, 152, 198, 27, 131, 85, 5, 179, 187, 63, 79, 10, 205, 122, 115, 209, 210, 30, 204, 59, 128, 129, 242, 19, 253, 188, 146, 232, 102, 186, 40, 69, 204, 243, 34, 57, 99, 61, 188, 50, 229, 180, 70, 244, 34, 95, 141, 50, 116, 190, 24, 253, 49, 68, 247, 145, 29, 97, 29, 93, 71, 37, 81, 148, 230, 32, 91, 125, 55, 193, 42, 123, 201, 25, 34, 58, 248, 128, 204, 225, 149, 38, 248, 29, 17, 230, 22, 236, 234, 207, 92, 124, 232, 225, 22, 96, 2, 32, 146, 27, 49, 2, 130, 1, 1, 0, 129, 62, 34, 61, 183, 242, 31, 37, 68, 193, 108, 144, 111, 133, 248, 130, 184, 239, 131, 182, 215, 72, 164, 176, 27, 84, 151, 48, 48, 14, 205, 95, 109, 131, 178, 240, 38, 50, 152, 55, 47, 32, 36, 11, 73, 128, 211, 85, 118, 199, 213, 46, 207, 132, 252, 74, 115, 166, 138, 97, 212, 2, 22, 59, 214, 25, 101, 121, 40, 191, 166, 28, 247, 60, 132, 84, 227, 76, 95, 212, 187, 69, 229, 59, 226, 20, 193, 119, 193, 61, 111, 105, 76, 124, 200, 61, 162, 6, 36, 246, 59, 82, 61, 59, 126, 234, 72, 160, 91, 135, 206, 135, 135, 7, 169, 158, 191, 180, 253, 220, 129, 242, 195, 220, 150, 124, 20, 51, 199, 19, 133, 154, 201, 43, 203, 14, 174, 61, 201, 64, 78, 229, 212, 10, 200, 133, 63, 197, 94, 142, 26, 20, 35, 57, 72, 207, 255, 33, 40, 50, 108, 231, 246, 211, 162, 182, 219, 8, 29, 60, 91, 93, 60, 106, 67, 167, 53, 22, 245, 61, 59, 166, 19, 191, 194, 101, 231, 240, 165, 235, 169, 33, 125, 125, 72, 213, 17, 183, 243, 27, 238, 173, 193, 212, 47, 37, 27, 98, 7, 174, 103, 242, 46, 163, 213, 235, 121, 62, 247, 135, 223, 232, 194, 143, 81, 130, 225, 147, 219, 213, 199, 226, 247, 13, 102, 100, 70, 127, 145, 136, 189, 22, 248, 123, 153, 111, 182, 87, 136, 102, 76, 9, 3, 123, 187, 243, 2, 130, 1, 0, 36, 121, 149, 41, 189, 115, 193, 110, 98, 69, 30, 145, 9, 231, 177, 98, 120, 118, 126, 102, 62, 220, 58, 207, 73, 211, 60, 15, 24, 107, 208, 95, 29, 107, 40, 190, 182, 84, 106, 17, 217, 198, 210, 27, 233, 227, 153, 252, 128, 181, 44, 145, 101, 156, 7, 209, 23, 149, 66, 78, 109, 145, 138, 13, 241, 174, 198, 3, 26, 222, 15, 241, 120, 176, 54, 190, 97, 80, 215, 99, 49, 62, 204, 135, 226, 32, 141, 102, 251, 32, 152, 108, 113, 237, 59, 142, 30, 185, 195, 135, 145, 1, 86, 115, 56, 253, 215, 186, 221, 202, 196, 36, 227, 118, 177, 130, 60, 59, 56, 190, 198, 157, 142, 18, 96, 43, 218, 199, 150, 42, 174, 44, 198, 65, 103, 139, 167, 177, 46, 26, 155, 248, 209, 56, 155, 209, 204, 42, 89, 224, 212, 75, 80, 135, 106, 203, 4, 81, 181, 85, 128, 247, 73, 134, 41, 48, 183, 57, 127, 28, 234, 26, 244, 177, 159, 113, 90, 249, 120, 32, 248, 134, 79, 99, 123, 155, 173, 201, 185, 216, 166, 32, 152, 181, 6, 154, 118, 18, 181, 245, 106, 25, 37, 146, 118, 16, 215, 30, 83, 96, 35, 154, 93, 0, 13, 5, 206, 156, 129, 147, 118, 87, 248, 155, 49, 135, 7, 39, 157, 226, 171, 96, 16, 112, 122, 173, 58, 145, 19, 6, 90, 11, 221, 109, 208, 16, 251, 188, 18, 120, 106, 170, 143, 149, 79, 192]), + jwk: { + kty: "RSA", + n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc", + e: "AQAB", + d: "uXPRXBhcE5-DWabBRKQuhxgU8ype5gTISWefeYP7U96ZHqu_sBByZ5ihdgyU9pgAZGVx4Ep9rnVKnH2lNr2zrP9Qhyqy99nM0aMxmypIWLAuP__DwLj4t99M4sU29c48CAq1egHfccSFjzpNuetOTCA71EJuokt70pm0OmGzgTyvjuR7VTLxd5PMXitBowSn8_cphmnFpT8tkTiuy8CH0R3DU7MOuINomDD1s8-yPBcVAVTPUnwJiauNuzestLQKMLlhT5wn-cAbYk36XRKdgkjSc2AkhHRl4WDqT1nzWYdh_DVIYSLiKSktkPO9ovMrRYiPtozfhl0m9SR9Ll0wXtcnnDlWXc_MSGpw18vmUBSJ4PIhkiFsvLn-db3wUkA8uve-iqqfk0sxlGWughWx03kGmZDmprWbXugCBHfsI4X93w4exznXH_tapxPnmjbhVUQR6p41MvO2lcHWPLwGJgLIoejBHpnn3TmMN0UjFZki7q9B_dJ3fXh0mX9DzAlC0sil1NgCPhMPq02393_giinQquMknrBvgKxGSfGUrDKuflCx611ZZlRM3R7YMX2OIy1g4DyhPzBVjxRMtm8PnIs3m3Hi-O-C_PHF93w9J8Wqd0yIw7SpavDqZXLPC6Cqi8K7MBZyVECXHtRj1bBqT-h_xZmFCDjSU0NqfOdgApE", + p: "9NrXwq4kY9kBBOwLoFZVQc4kJI_NbKa_W9FLdQdRIbMsZZHXJ3XDUR9vJAcaaR75WwIC7X6N55nVtWTq28Bys9flJ9RrCTfciOntHEphBhYaL5ZTUl-6khYmsOf_psff2VaOOCvHGff5ejuOmBQxkw2E-cv7knRgWFHoLWpku2NJIMuGHt9ks7OAUfIZVYl9YJnw4FYUzhgaxemknjLeZ8XTkGW2zckzF-d95YI9i8zD80Umubsw-YxriSfqFQ0rGHBsbQ8ZOTd_KJju42BWnXIjNDYmjFUqdzVjI4XQ8EGrCEf_8_iwphGyXD7LOJ4fqd97B3bYpoRTPnCgY_SEHQ", + q: "5J758_NeKr1XPZiLxXohYQQnh0Lb4QtGZ1xzCgjhBQLcIBeTOG_tYjCues9tmLt93LpJfypSJ-SjDLwkR2s069_IByYGpxyeGtV-ulqYhSw1nD2CXKMDGyO5jXDs9tJrS_UhfobXKQH03CRdFugyPkSNmXY-AafFynG7xLr7oYBC05FnhUXPm3VBTPt9K-BpqwYd_h9vkAWeprSPo83UlwcLMupSJY9LaHxhRdz2yi0ZKNwXXHRwcszGjDBvvzUcCYbqWqjzbEvFY6KtH8Jh4LhM46rHaoEOTernJsDF6a6W8Df88RthqTExcwnaQf0O_dlbjSxEIPfbxx8t1EQugw", + dp: "4Y7Hu5tYAnLhMXuQqj9dgqU3PkcKYdCp7xc6f7Ah2P2JJHfYz4z4RD7Ez1eLyNKzulZ8A_PVHUjlSZiRkaYTBAEaJDrV70P6cFWuC6WpA0ZREQ1V7EgrQnANbGILa8QsPbYyhSQu4YlB1IwQq5_OmzyVBtgWA7AZIMMzMsMT0FuB_if-gWohBjmRN-vh0p45VUf6UW568-_YmgDFmMYbg1UFs7s_TwrNenPR0h7MO4CB8hP9vJLoZrooRczzIjljPbwy5bRG9CJfjTJ0vhj9MUT3kR1hHV1HJVGU5iBbfTfBKnvJGSI6-IDM4ZUm-B0R5hbs6s9cfOjhFmACIJIbMQ", + dq: "gT4iPbfyHyVEwWyQb4X4grjvg7bXSKSwG1SXMDAOzV9tg7LwJjKYNy8gJAtJgNNVdsfVLs-E_Epzpoph1AIWO9YZZXkov6Yc9zyEVONMX9S7ReU74hTBd8E9b2lMfMg9ogYk9jtSPTt-6kigW4fOh4cHqZ6_tP3cgfLD3JZ8FDPHE4WaySvLDq49yUBO5dQKyIU_xV6OGhQjOUjP_yEoMmzn9tOittsIHTxbXTxqQ6c1FvU9O6YTv8Jl5_Cl66khfX1I1RG38xvurcHULyUbYgeuZ_Iuo9XreT73h9_owo9RguGT29XH4vcNZmRGf5GIvRb4e5lvtleIZkwJA3u78w", + qi: "JHmVKb1zwW5iRR6RCeexYnh2fmY-3DrPSdM8Dxhr0F8dayi-tlRqEdnG0hvp45n8gLUskWWcB9EXlUJObZGKDfGuxgMa3g_xeLA2vmFQ12MxPsyH4iCNZvsgmGxx7TuOHrnDh5EBVnM4_de63crEJON2sYI8Ozi-xp2OEmAr2seWKq4sxkFni6exLhqb-NE4m9HMKlng1EtQh2rLBFG1VYD3SYYpMLc5fxzqGvSxn3Fa-Xgg-IZPY3ubrcm52KYgmLUGmnYStfVqGSWSdhDXHlNgI5pdAA0FzpyBk3ZX-JsxhwcnneKrYBBweq06kRMGWgvdbdAQ-7wSeGqqj5VPwA" + } + }, + + }; + + // combinations to test + var testVectors = [ + {name: "RSA-OAEP", privateUsages: ["decrypt", "unwrapKey"], publicUsages: ["encrypt", "wrapKey"]}, + {name: "RSA-PSS", privateUsages: ["sign"], publicUsages: ["verify"]}, + {name: "RSASSA-PKCS1-v1_5", privateUsages: ["sign"], publicUsages: ["verify"]} + ]; + + // TESTS ARE HERE: + // Test every test vector, along with all available key data + testVectors.forEach(function(vector) { + sizes.forEach(function(size) { + + hashes.forEach(function(hash) { + [true, false].forEach(function(extractable) { + + // Test public keys first + allValidUsages(vector.publicUsages, true).forEach(function(usages) { + ['spki', 'jwk'].forEach(function(format) { + var algorithm = {name: vector.name, hash: hash}; + var data = keyData[size]; + if (format === "jwk") { // Not all fields used for public keys + data = {jwk: {kty: keyData[size].jwk.kty, n: keyData[size].jwk.n, e: keyData[size].jwk.e}}; + } + + testFormat(format, algorithm, data, size, usages, extractable); + }); + + }); + + // Next, test private keys + ['pkcs8', 'jwk'].forEach(function(format) { + var algorithm = {name: vector.name, hash: hash}; + var data = keyData[size]; + allValidUsages(vector.privateUsages).forEach(function(usages) { + testFormat(format, algorithm, data, size, usages, extractable); + }); + testEmptyUsages(format, algorithm, data, size, extractable); + }); + }); + }); + + }); + }); + + + // Test importKey with a given key format and other parameters. If + // extrable is true, export the key and verify that it matches the input. + function testFormat(format, algorithm, keyData, keySize, usages, extractable) { + promise_test(function(test) { + return subtle.importKey(format, keyData[format], algorithm, extractable, usages). + then(function(key) { + assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, algorithm, extractable, usages, (format === 'pkcs8' || (format === 'jwk' && keyData[format].d)) ? 'private' : 'public'); + if (!extractable) { + return; + } + + return subtle.exportKey(format, key). + then(function(result) { + if (format !== "jwk") { + assert_true(equalBuffers(keyData[format], result), "Round trip works"); + } else { + assert_true(equalJwk(keyData[format], result), "Round trip works"); + } + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, "Good parameters: " + keySize.toString() + " bits " + parameterString(format, keyData[format], algorithm, extractable, usages)); + } + + // Test importKey with a given key format and other parameters but with empty usages. + // Should fail with SyntaxError + function testEmptyUsages(format, algorithm, keyData, keySize, extractable) { + const usages = []; + promise_test(function(test) { + return subtle.importKey(format, keyData[format], algorithm, extractable, usages). + then(function(key) { + assert_unreached("importKey succeeded but should have failed with SyntaxError"); + }, function(err) { + assert_equals(err.name, "SyntaxError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, "Empty Usages: " + keySize.toString() + " bits " + parameterString(format, keyData, algorithm, extractable, usages)); + } + + + + // Helper methods follow: + + // Are two array buffers the same? + function equalBuffers(a, b) { + if (a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + for (var i=0; i Date: Fri, 13 Dec 2024 22:36:18 +0100 Subject: [PATCH 195/237] LibCrypto+LibWeb: Allow serializing key info without params Previously, if `nullptr` was passed as params for `wrap_in_private_key_info` or `wrap_in_subject_public_key_info` an ASN1 null was serialized. This was not the intended behaviour for many. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibCrypto/PK/PK.h | 98 +++++++++++++++++++ Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 12 +-- .../okp_importKey_Ed25519.https.any.txt | 23 +++-- .../okp_importKey_X25519.https.any.txt | 8 +- .../okp_importKey_X448.https.any.txt | 8 +- 5 files changed, 123 insertions(+), 26 deletions(-) diff --git a/Libraries/LibCrypto/PK/PK.h b/Libraries/LibCrypto/PK/PK.h index 63b668185ea4b..4e24f4a2af73b 100644 --- a/Libraries/LibCrypto/PK/PK.h +++ b/Libraries/LibCrypto/PK/PK.h @@ -13,6 +13,29 @@ namespace Crypto::PK { +template +ErrorOr wrap_in_private_key_info(ByteBuffer key, Span algorithm_identifier) +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(0x00u)); // version + + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(algorithm_identifier)); // algorithm + + return {}; + })); + + // PrivateKey + TRY(encoder.write(key)); + + return {}; + })); + + return encoder.finish(); +} + template ErrorOr wrap_in_private_key_info(ByteBuffer key, Span algorithm_identifier, Params params) { @@ -38,6 +61,33 @@ ErrorOr wrap_in_private_key_info(ByteBuffer key, Span alg return encoder.finish(); } +template +ErrorOr wrap_in_private_key_info(ExportableKey key, Span algorithm_identifier) +requires requires(ExportableKey k) { + k.export_as_der(); +} +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(0x00u)); // version + + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(algorithm_identifier)); // algorithm + + return {}; + })); + + // PrivateKey + auto data = TRY(key.export_as_der()); + TRY(encoder.write(data)); + + return {}; + })); + + return encoder.finish(); +} + template ErrorOr wrap_in_private_key_info(ExportableKey key, Span algorithm_identifier, Params params) requires requires(ExportableKey k) { @@ -67,6 +117,28 @@ requires requires(ExportableKey k) { return encoder.finish(); } +template +ErrorOr wrap_in_subject_public_key_info(ByteBuffer key, Span algorithm_identifier) +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(algorithm_identifier)); // algorithm + + return {}; + })); + + // subjectPublicKey + auto bitstring = ::Crypto::ASN1::BitStringView(key, 0); + TRY(encoder.write(bitstring)); + + return {}; + })); + + return encoder.finish(); +} + template ErrorOr wrap_in_subject_public_key_info(ByteBuffer key, Span algorithm_identifier, ParamsType const& params) { @@ -91,6 +163,32 @@ ErrorOr wrap_in_subject_public_key_info(ByteBuffer key, Span +ErrorOr wrap_in_subject_public_key_info(ExportableKey key, Span algorithm_identifier) +requires requires(ExportableKey k) { + k.export_as_der(); +} +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(algorithm_identifier)); // algorithm + + return {}; + })); + + // subjectPublicKey + auto data = TRY(key.export_as_der()); + auto bitstring = ::Crypto::ASN1::BitStringView(data, 0); + TRY(encoder.write(bitstring)); + + return {}; + })); + + return encoder.finish(); +} + template ErrorOr wrap_in_subject_public_key_info(ExportableKey key, Span algorithm_identifier, ParamsType const& params) requires requires(ExportableKey k) { diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 578f7b604398a..233a694bd2043 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -4491,7 +4491,7 @@ WebIDL::ExceptionOr> ED25519::export_key(Bindings::KeyFormat // * Set the algorithm object identifier to the id-Ed25519 OID defined in [RFC8410]. // * Set the subjectPublicKey field to keyData. auto ed25519_oid = ::Crypto::ASN1::ed25519_oid; - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_subject_public_key_info(key_data, ed25519_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_subject_public_key_info(key_data, ed25519_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, move(data)); @@ -4514,7 +4514,7 @@ WebIDL::ExceptionOr> ED25519::export_key(Bindings::KeyFormat TRY_OR_THROW_OOM(vm, encoder.write(key_data.bytes())); auto ed25519_oid = ::Crypto::ASN1::ed25519_oid; - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(encoder.finish(), ed25519_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(encoder.finish(), ed25519_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, move(data)); @@ -5183,7 +5183,7 @@ WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat // Set the algorithm object identifier to the id-X25519 OID defined in [RFC8410]. // Set the subjectPublicKey field to keyData. auto public_key = handle.get(); - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_subject_public_key_info(public_key, ::Crypto::ASN1::x25519_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_subject_public_key_info(public_key, ::Crypto::ASN1::x25519_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. result = JS::ArrayBuffer::create(m_realm, data); @@ -5202,7 +5202,7 @@ WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat // Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410], // that represents the X25519 private key represented by the [[handle]] internal slot of key auto private_key = handle.get(); - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(private_key, ::Crypto::ASN1::x25519_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(private_key, ::Crypto::ASN1::x25519_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. result = JS::ArrayBuffer::create(m_realm, data); @@ -5427,7 +5427,7 @@ WebIDL::ExceptionOr> X448::export_key(Bindings::KeyFormat fo // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410]. // * Set the subjectPublicKey field to keyData. auto x448_oid = ::Crypto::ASN1::x448_oid; - auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_subject_public_key_info(key_data, x448_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_subject_public_key_info(key_data, x448_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, data); @@ -5445,7 +5445,7 @@ WebIDL::ExceptionOr> X448::export_key(Bindings::KeyFormat fo // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410]. // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410], that represents the X448 private key represented by the [[handle]] internal slot of key auto x448_oid = ::Crypto::ASN1::x448_oid; - auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_private_key_info(key_data, x448_oid, nullptr)); + auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_private_key_info(key_data, x448_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, data); diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt index f55036a7ff2aa..0bb7b8f297301 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_Ed25519.https.any.txt @@ -2,40 +2,39 @@ Harness status: OK Found 62 tests -52 Pass -10 Fail -Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify]) -Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify]) +62 Pass +Pass Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify]) +Pass Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify]) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, [verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, [verify]) -Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, []) -Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, []) +Pass Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, []) +Pass Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, []) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, []) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, []) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, []) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, []) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, []) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, []) -Fail Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify, verify]) -Fail Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify, verify]) +Pass Good parameters: Ed25519 bits (spki, buffer(44), {name: Ed25519}, true, [verify, verify]) +Pass Good parameters: Ed25519 bits (spki, buffer(44), Ed25519, true, [verify, verify]) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) Pass Good parameters: Ed25519 bits (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), {name: Ed25519}, true, [verify, verify]) Pass Good parameters: Ed25519 bits (raw, buffer(32), Ed25519, true, [verify, verify]) -Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign]) -Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign]) +Pass Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign]) +Pass Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign]) Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign]) -Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign, sign]) -Fail Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign, sign]) +Pass Good parameters: Ed25519 bits (pkcs8, buffer(48), {name: Ed25519}, true, [sign, sign]) +Pass Good parameters: Ed25519 bits (pkcs8, buffer(48), Ed25519, true, [sign, sign]) Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) Pass Good parameters: Ed25519 bits (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign]) Pass Good parameters with ignored JWK alg: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign]) diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt index f02063956973d..71448293d7ac7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt @@ -2,10 +2,10 @@ Harness status: OK Found 54 tests -24 Pass -30 Fail -Fail Good parameters: X25519 bits (spki, buffer(44), {name: X25519}, true, []) -Fail Good parameters: X25519 bits (spki, buffer(44), X25519, true, []) +26 Pass +28 Fail +Pass Good parameters: X25519 bits (spki, buffer(44), {name: X25519}, true, []) +Pass Good parameters: X25519 bits (spki, buffer(44), X25519, true, []) Fail Good parameters: X25519 bits (jwk, object(kty, crv, x), {name: X25519}, true, []) Fail Good parameters: X25519 bits (jwk, object(kty, crv, x), X25519, true, []) Fail Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), {name: X25519}, true, []) diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt index 29405850640f9..1b533342cfeb8 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt @@ -2,10 +2,10 @@ Harness status: OK Found 54 tests -24 Pass -30 Fail -Fail Good parameters: X448 bits (spki, buffer(68), {name: X448}, true, []) -Fail Good parameters: X448 bits (spki, buffer(68), X448, true, []) +26 Pass +28 Fail +Pass Good parameters: X448 bits (spki, buffer(68), {name: X448}, true, []) +Pass Good parameters: X448 bits (spki, buffer(68), X448, true, []) Fail Good parameters: X448 bits (jwk, object(kty, crv, x), {name: X448}, true, []) Fail Good parameters: X448 bits (jwk, object(kty, crv, x), X448, true, []) Fail Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), {name: X448}, true, []) From ac99e2791f47869aa6e0777c54d4fb5b6673cba2 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 11:11:57 +0100 Subject: [PATCH 196/237] LibWeb: Fix `X25519` JWK key export format The presence of padding in the base64 fields made plenty of WPT tests fail. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 6 +-- .../okp_importKey_X25519.https.any.txt | 44 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 233a694bd2043..28ea06c1d2477 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -5222,7 +5222,7 @@ WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat // 4. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037]. if (key->type() == Bindings::KeyType::Public) { auto public_key = handle.get(); - jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key)); + jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key, AK::OmitPadding::Yes)); } else { // The "x" parameter of the "epk" field is set as follows: // Apply the appropriate ECDH function to the ephemeral private key (as scalar input) @@ -5230,14 +5230,14 @@ WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat // The base64url encoding of the output is the value for the "x" parameter of the "epk" field. ::Crypto::Curves::X25519 curve; auto public_key = TRY_OR_THROW_OOM(vm, curve.generate_public_key(handle.get())); - jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key)); + jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key, AK::OmitPadding::Yes)); } // 5. If the [[type]] internal slot of key is "private" if (key->type() == Bindings::KeyType::Private) { // 1. Set the d attribute of jwk according to the definition in Section 2 of [RFC8037]. auto private_key = handle.get(); - jwk.d = TRY_OR_THROW_OOM(vm, encode_base64url(private_key)); + jwk.d = TRY_OR_THROW_OOM(vm, encode_base64url(private_key, AK::OmitPadding::Yes)); } // 6. Set the key_ops attribute of jwk to the usages attribute of key. diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt index 71448293d7ac7..5055524d6c984 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt @@ -2,40 +2,40 @@ Harness status: OK Found 54 tests -26 Pass -28 Fail +46 Pass +8 Fail Pass Good parameters: X25519 bits (spki, buffer(44), {name: X25519}, true, []) Pass Good parameters: X25519 bits (spki, buffer(44), X25519, true, []) -Fail Good parameters: X25519 bits (jwk, object(kty, crv, x), {name: X25519}, true, []) -Fail Good parameters: X25519 bits (jwk, object(kty, crv, x), X25519, true, []) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), {name: X25519}, true, []) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), X25519, true, []) +Pass Good parameters: X25519 bits (jwk, object(kty, crv, x), {name: X25519}, true, []) +Pass Good parameters: X25519 bits (jwk, object(kty, crv, x), X25519, true, []) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), {name: X25519}, true, []) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), X25519, true, []) Pass Good parameters: X25519 bits (raw, buffer(32), {name: X25519}, true, []) Pass Good parameters: X25519 bits (raw, buffer(32), X25519, true, []) Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits, deriveKey]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits, deriveKey]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X25519 bits (spki, buffer(44), {name: X25519}, false, []) Pass Good parameters: X25519 bits (spki, buffer(44), X25519, false, []) Pass Good parameters: X25519 bits (jwk, object(kty, crv, x), {name: X25519}, false, []) From 880401030d09b26a5baac7bd859c13b1584962ff Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 11:12:14 +0100 Subject: [PATCH 197/237] LibWeb: Fix `X448` JWK key export format The presence of padding in the base64 fields made plenty of WPT tests fail. Additionally, export was performed with the wrong public key. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 10 ++++- .../okp_importKey_X448.https.any.txt | 44 +++++++++---------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 28ea06c1d2477..4f74fc76f3704 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -5463,12 +5463,18 @@ WebIDL::ExceptionOr> X448::export_key(Bindings::KeyFormat fo jwk.crv = "X448"_string; // 4. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037]. - jwk.x = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_data)); + if (key->type() == Bindings::KeyType::Public) { + jwk.x = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_data, AK::OmitPadding::Yes)); + } else { + ::Crypto::Curves::X448 curve; + auto public_key = TRY_OR_THROW_OOM(m_realm->vm(), curve.generate_public_key(key_data)); + jwk.x = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(public_key, AK::OmitPadding::Yes)); + } // 5. If the [[type]] internal slot of key is "private" if (key->type() == Bindings::KeyType::Private) { // 1. Set the d attribute of jwk according to the definition in Section 2 of [RFC8037]. - jwk.d = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_data)); + jwk.d = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_data, AK::OmitPadding::Yes)); } // 6. Set the key_ops attribute of jwk to the usages attribute of key. diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt index 1b533342cfeb8..3b55799e853f7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt @@ -2,40 +2,40 @@ Harness status: OK Found 54 tests -26 Pass -28 Fail +46 Pass +8 Fail Pass Good parameters: X448 bits (spki, buffer(68), {name: X448}, true, []) Pass Good parameters: X448 bits (spki, buffer(68), X448, true, []) -Fail Good parameters: X448 bits (jwk, object(kty, crv, x), {name: X448}, true, []) -Fail Good parameters: X448 bits (jwk, object(kty, crv, x), X448, true, []) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), {name: X448}, true, []) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), X448, true, []) +Pass Good parameters: X448 bits (jwk, object(kty, crv, x), {name: X448}, true, []) +Pass Good parameters: X448 bits (jwk, object(kty, crv, x), X448, true, []) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), {name: X448}, true, []) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), X448, true, []) Pass Good parameters: X448 bits (raw, buffer(56), {name: X448}, true, []) Pass Good parameters: X448 bits (raw, buffer(56), X448, true, []) Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey]) Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits, deriveKey]) Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits, deriveKey]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits]) Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X448 bits (spki, buffer(68), {name: X448}, false, []) Pass Good parameters: X448 bits (spki, buffer(68), X448, false, []) Pass Good parameters: X448 bits (jwk, object(kty, crv, x), {name: X448}, false, []) From 89f1f3f31ca82089e0375f6cd1209f334d9d67b2 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 11:18:57 +0100 Subject: [PATCH 198/237] LibWeb: Fix `X25519` PCKS#8 key export format The ASN1 structure for PCKS#8 was wrong and missing one wrapping of the key in a OctetString. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 5 ++++- .../okp_importKey_X25519.https.any.txt | 19 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 4f74fc76f3704..d3fcb386d2aab 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -5202,7 +5202,10 @@ WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat // Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410], // that represents the X25519 private key represented by the [[handle]] internal slot of key auto private_key = handle.get(); - auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(private_key, ::Crypto::ASN1::x25519_oid)); + + ::Crypto::ASN1::Encoder encoder; + TRY_OR_THROW_OOM(vm, encoder.write(private_key)); + auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(encoder.finish(), ::Crypto::ASN1::x25519_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. result = JS::ArrayBuffer::create(m_realm, data); diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt index 5055524d6c984..718fa31ce9810 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X25519.https.any.txt @@ -2,8 +2,7 @@ Harness status: OK Found 54 tests -46 Pass -8 Fail +54 Pass Pass Good parameters: X25519 bits (spki, buffer(44), {name: X25519}, true, []) Pass Good parameters: X25519 bits (spki, buffer(44), X25519, true, []) Pass Good parameters: X25519 bits (jwk, object(kty, crv, x), {name: X25519}, true, []) @@ -12,26 +11,26 @@ Pass Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), {na Pass Good parameters with ignored JWK alg: X25519 (jwk, object(kty, crv, x), X25519, true, []) Pass Good parameters: X25519 bits (raw, buffer(32), {name: X25519}, true, []) Pass Good parameters: X25519 bits (raw, buffer(32), X25519, true, []) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveKey]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits, deriveKey]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits, deriveKey]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits, deriveKey]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits, deriveKey]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits, deriveKey]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits, deriveKey]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveBits]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveBits]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveBits]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), X25519, true, [deriveBits]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X25519 bits (pkcs8, buffer(48), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X25519 bits (jwk, object(crv, d, x, kty), X25519, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters with ignored JWK alg: X25519 (jwk, object(crv, d, x, kty), {name: X25519}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) From 06733bea48bd760fd99f1f15728ab11bccc9a423 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 11:19:16 +0100 Subject: [PATCH 199/237] LibWeb: Fix `X448` PCKS#8 key export format The ASN1 structure for PCKS#8 was wrong and missing one wrapping of the key in a OctetString. The issue was discovered while implementing `wrapKey` and `unwrapKey` in the next commits. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 5 ++++- .../okp_importKey_X448.https.any.txt | 19 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index d3fcb386d2aab..86bd341380073 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -5447,8 +5447,11 @@ WebIDL::ExceptionOr> X448::export_key(Bindings::KeyFormat fo // * Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type with the following properties: // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410]. // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410], that represents the X448 private key represented by the [[handle]] internal slot of key + ::Crypto::ASN1::Encoder encoder; + TRY_OR_THROW_OOM(m_realm->vm(), encoder.write(key_data.bytes())); + auto x448_oid = ::Crypto::ASN1::x448_oid; - auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_private_key_info(key_data, x448_oid)); + auto data = TRY_OR_THROW_OOM(m_realm->vm(), ::Crypto::PK::wrap_in_private_key_info(encoder.finish(), x448_oid)); // 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data. return JS::ArrayBuffer::create(m_realm, data); diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt index 3b55799e853f7..c93d07571207a 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/okp_importKey_X448.https.any.txt @@ -2,8 +2,7 @@ Harness status: OK Found 54 tests -46 Pass -8 Fail +54 Pass Pass Good parameters: X448 bits (spki, buffer(68), {name: X448}, true, []) Pass Good parameters: X448 bits (spki, buffer(68), X448, true, []) Pass Good parameters: X448 bits (jwk, object(kty, crv, x), {name: X448}, true, []) @@ -12,26 +11,26 @@ Pass Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), {name Pass Good parameters with ignored JWK alg: X448 (jwk, object(kty, crv, x), X448, true, []) Pass Good parameters: X448 bits (raw, buffer(56), {name: X448}, true, []) Pass Good parameters: X448 bits (raw, buffer(56), X448, true, []) -Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveKey]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits, deriveKey]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits, deriveKey]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits, deriveKey]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits, deriveKey]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits, deriveKey]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits, deriveKey]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveBits]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveBits]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveBits]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), X448, true, [deriveBits]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) -Fail Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) +Pass Good parameters: X448 bits (pkcs8, buffer(72), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters: X448 bits (jwk, object(crv, d, x, kty), X448, true, [deriveKey, deriveBits, deriveKey, deriveBits]) Pass Good parameters with ignored JWK alg: X448 (jwk, object(crv, d, x, kty), {name: X448}, true, [deriveKey, deriveBits, deriveKey, deriveBits]) From c1a65f3d537e3042c2996c5a4a174232bd4f9584 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 12:22:23 +0100 Subject: [PATCH 200/237] LibWeb: Parse `JsonWebKey` from string Add the ability to parse a `JsonWebKey` structure from a raw string. --- Libraries/LibWeb/Crypto/CryptoBindings.cpp | 74 ++++++++++++++++++++++ Libraries/LibWeb/Crypto/CryptoBindings.h | 2 + 2 files changed, 76 insertions(+) diff --git a/Libraries/LibWeb/Crypto/CryptoBindings.cpp b/Libraries/LibWeb/Crypto/CryptoBindings.cpp index 2e7e7bde7fd7c..bce65d231baa3 100644 --- a/Libraries/LibWeb/Crypto/CryptoBindings.cpp +++ b/Libraries/LibWeb/Crypto/CryptoBindings.cpp @@ -1,15 +1,89 @@ /* * Copyright (c) 2024, Andrew Kaster + * Copyright (c) 2024, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include #include +#include namespace Web::Bindings { +#define JWK_PARSE_STRING_PROPERTY(name) \ + if (json_object.has_string(#name##sv)) { \ + key.name = TRY_OR_THROW_OOM(vm, String::from_byte_string(*json_object.get_byte_string(#name##sv))); \ + } + +JS::ThrowCompletionOr JsonWebKey::parse(JS::Realm& realm, ReadonlyBytes data) +{ + auto& vm = realm.vm(); + // 1. Let data be the sequence of bytes to be parsed. + + // 2. Let json be the Unicode string that results from interpreting data according to UTF-8. + // 3. Convert json to UTF-16. + auto json = TRY_OR_THROW_OOM(vm, String::from_utf8(data)); + + // 4. Let result be the object literal that results from executing the JSON.parse internal function + // in the context of a new global object, with text argument set to a JavaScript String containing json. + auto maybe_json_value = JsonValue::from_string(json); + if (maybe_json_value.is_error()) + return vm.throw_completion(JS::ErrorType::JsonMalformed); + + auto json_value = maybe_json_value.release_value(); + if (!json_value.is_object()) { + return vm.throw_completion("JSON value is not an object"_string); + } + + auto& json_object = json_value.as_object(); + + // 5. Let key be the result of converting result to the IDL dictionary type of JsonWebKey. + JsonWebKey key {}; + JWK_PARSE_STRING_PROPERTY(kty); + JWK_PARSE_STRING_PROPERTY(use); + JWK_PARSE_STRING_PROPERTY(alg); + JWK_PARSE_STRING_PROPERTY(crv); + JWK_PARSE_STRING_PROPERTY(x); + JWK_PARSE_STRING_PROPERTY(y); + JWK_PARSE_STRING_PROPERTY(d); + JWK_PARSE_STRING_PROPERTY(n); + JWK_PARSE_STRING_PROPERTY(e); + JWK_PARSE_STRING_PROPERTY(p); + JWK_PARSE_STRING_PROPERTY(q); + JWK_PARSE_STRING_PROPERTY(dp); + JWK_PARSE_STRING_PROPERTY(dq); + JWK_PARSE_STRING_PROPERTY(qi); + JWK_PARSE_STRING_PROPERTY(k); + + key.ext = json_object.get_bool("ext"sv); + + if (json_object.has_array("key_ops"sv)) { + auto key_ops = *json_object.get_array("key_ops"sv); + key.key_ops = Vector {}; + TRY_OR_THROW_OOM(vm, key.key_ops->try_ensure_capacity(key_ops.size())); + + TRY(key_ops.try_for_each([&key, &vm](auto value) -> JS::Completion { + key.key_ops->append(TRY_OR_THROW_OOM(vm, String::from_byte_string(value.as_string()))); + return {}; + })); + } + + if (json_object.has("oth"sv)) + TODO(); + + // 6. If the kty field of key is not defined, then throw a DataError. + if (!key.kty.has_value()) + return vm.throw_completion("kty field is not defined"_string); + + // 7. Return key. + return key; +} + +#undef JWK_PARSE_STRING_PROPERTY JS::ThrowCompletionOr> JsonWebKey::to_object(JS::Realm& realm) { diff --git a/Libraries/LibWeb/Crypto/CryptoBindings.h b/Libraries/LibWeb/Crypto/CryptoBindings.h index 89e98fdff5c93..0cb69019c88c2 100644 --- a/Libraries/LibWeb/Crypto/CryptoBindings.h +++ b/Libraries/LibWeb/Crypto/CryptoBindings.h @@ -44,6 +44,8 @@ struct JsonWebKey { Optional k; JS::ThrowCompletionOr> to_object(JS::Realm&); + + static JS::ThrowCompletionOr parse(JS::Realm& realm, ReadonlyBytes data); }; } From 584cbcf3efd330ec72f8ae6d1ba7d12742d5bf33 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 14 Dec 2024 12:23:52 +0100 Subject: [PATCH 201/237] LibWeb: Implement `wrapKey` and `unwrapKey` methods This implements the last WebCryptoAPI methods `wrapKey` and `unwrapKey`. Most of the functionality is already there because they rely on `encrypt` and `decrypt`. The only test failures are for `AES-GCM` which is not implemented yet. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.h | 10 + Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 289 ++++++++++ Libraries/LibWeb/Crypto/SubtleCrypto.h | 3 + Libraries/LibWeb/Crypto/SubtleCrypto.idl | 4 +- .../wrapKey_unwrapKey.https.any.txt | 250 ++++++++ .../wrapKey_unwrapKey.https.any.html | 17 + .../wrapKey_unwrapKey.https.any.js | 536 ++++++++++++++++++ .../wrapKey_unwrapKey_vectors.js | 114 ++++ 8 files changed, 1221 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey_vectors.js diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 1bad8b7ca9979..9dd1954c78424 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -337,6 +337,16 @@ class AlgorithmMethods { return WebIDL::NotSupportedError::create(m_realm, "getKeyLength is not supported"_string); } + virtual WebIDL::ExceptionOr> wrap_key(AlgorithmParams const&, GC::Ref, ByteBuffer const&) + { + return WebIDL::NotSupportedError::create(m_realm, "wrapKey is not supported"_string); + } + + virtual WebIDL::ExceptionOr> unwrap_key(AlgorithmParams const&, GC::Ref, ByteBuffer const&) + { + return WebIDL::NotSupportedError::create(m_realm, "unwwrapKey is not supported"_string); + } + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new AlgorithmMethods(realm)); } protected: diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index e032ae579cf88..cf283ec500d69 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -6,9 +6,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include +#include #include #include #include @@ -744,6 +746,293 @@ JS::ThrowCompletionOr> SubtleCrypto::derive_key(Algorit return promise; } +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey +JS::ThrowCompletionOr> SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC::Ref key, GC::Ref wrapping_key, AlgorithmIdentifier algorithm) +{ + auto& realm = this->realm(); + // 1. Let format, key, wrappingKey and algorithm be the format, key, wrappingKey and wrapAlgorithm parameters passed to the wrapKey() method, respectively. + + StringView operation; + auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr { + // 2. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "wrapKey". + auto normalized_algorithm_wrap_key_or_error = normalize_an_algorithm(realm, algorithm, "wrapKey"_string); + + // 3. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "encrypt". + // 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + if (normalized_algorithm_wrap_key_or_error.is_error()) { + auto normalized_algorithm_encrypt_or_error = normalize_an_algorithm(realm, algorithm, "encrypt"_string); + if (normalized_algorithm_encrypt_or_error.is_error()) + return normalized_algorithm_encrypt_or_error.release_error(); + + operation = "encrypt"sv; + return normalized_algorithm_encrypt_or_error.release_value(); + } else { + operation = "wrapKey"sv; + return normalized_algorithm_wrap_key_or_error.release_value(); + } + }(); + if (normalized_algorithm_or_error.is_error()) + return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error()); + + auto normalized_algorithm = normalized_algorithm_or_error.release_value(); + + // 5. Let promise be a new Promise. + auto promise = WebIDL::create_promise(realm); + + // 6. Return promise and perform the remaining steps in parallel. + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, wrapping_key = move(wrapping_key), key = move(key), format, operation]() mutable -> void { + HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + // 7. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm. + + // 8. If the name member of normalizedAlgorithm is not equal to the name attribute + // of the [[algorithm]] internal slot of wrappingKey then throw an InvalidAccessError. + if (normalized_algorithm.parameter->name != wrapping_key->algorithm_name()) { + WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string)); + return; + } + + // 9. If the [[usages]] internal slot of wrappingKey does not contain an entry that is "wrapKey", then throw an InvalidAccessError. + if (!wrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Wrapkey)) { + WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support wrapping keys"_string)); + return; + } + + // 10. If the algorithm identified by the [[algorithm]] internal slot of key does not support the export key operation, then throw a NotSupportedError. + // Note: Handled by the base AlgorithmMethods implementation + + // 11. If the [[extractable]] internal slot of key is false, then throw an InvalidAccessError. + if (!key->extractable()) { + WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key is not extractable"_string)); + return; + } + + // 12. Let key be the result of performing the export key operation specified the [[algorithm]] internal slot of key using key and format. + // NOTE: The spec does not mention we need to normalize this, but it's the only way we have to get to export_key. + auto& key_algorithm = verify_cast(*key->algorithm()); + auto normalized_key_algorithm = normalize_an_algorithm(realm, key_algorithm.name(), "exportKey"_string); + if (normalized_key_algorithm.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), normalized_key_algorithm.release_error()).release_value().value()); + return; + } + + auto key_data_or_error = normalized_key_algorithm.release_value().methods->export_key(format, key); + if (key_data_or_error.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_data_or_error.release_error()).release_value().value()); + return; + } + + auto key_data = key_data_or_error.release_value(); + + ByteBuffer bytes; + // 13. If format is equal to the strings "raw", "pkcs8", or "spki": + if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) { + // Set bytes be set to key. + bytes = verify_cast(*key_data).buffer(); + } + + // If format is equal to the string "jwk": + else if (format == Bindings::KeyFormat::Jwk) { + // 1. Convert key to an ECMAScript Object, as specified in [WEBIDL], + // performing the conversion in the context of a new global object. + // 2. Let json be the result of representing key as a UTF-16 string conforming to the JSON grammar; + // for example, by executing the JSON.stringify algorithm specified in [ECMA-262] in the context of a new global object. + auto maybe_json = JS::JSONObject::stringify_impl(realm.vm(), key_data, JS::Value {}, JS::Value {}); + if (maybe_json.is_error()) { + WebIDL::reject_promise(realm, promise, maybe_json.release_error().release_value().value()); + return; + } + + // 3. Let bytes be the result of UTF-8 encoding json. + bytes = maybe_json.release_value()->to_byte_buffer(); + } else { + VERIFY_NOT_REACHED(); + } + + JS::Value result; + // 14. If normalizedAlgorithm supports the wrap key operation: + if (operation == "wrapKey") { + // Let result be the result of performing the wrap key operation specified by normalizedAlgorithm + // using algorithm, wrappingKey as key and bytes as plaintext. + auto result_or_error = normalized_algorithm.methods->wrap_key(*normalized_algorithm.parameter, wrapping_key, bytes); + if (result_or_error.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + return; + } + + result = result_or_error.release_value(); + } + + // Otherwise, if normalizedAlgorithm supports the encrypt operation: + else if (operation == "encrypt") { + // Let result be the result of performing the encrypt operation specified by normalizedAlgorithm + // using algorithm, wrappingKey as key and bytes as plaintext. + auto result_or_error = normalized_algorithm.methods->encrypt(*normalized_algorithm.parameter, wrapping_key, bytes); + if (result_or_error.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + return; + } + + result = result_or_error.release_value(); + } + + // Otherwise: + else { + // throw a NotSupportedError. + WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string)); + return; + } + + // 15. Resolve promise with result. + WebIDL::resolve_promise(realm, promise, result); + })); + + return promise; +} + +// https://w3c.github.io/webcrypto/#SubtleCrypto-method-unwrapKey +JS::ThrowCompletionOr> SubtleCrypto::unwrap_key(Bindings::KeyFormat format, KeyDataType wrapped_key, GC::Ref unwrapping_key, AlgorithmIdentifier algorithm, AlgorithmIdentifier unwrapped_key_algorithm, bool extractable, Vector key_usages) +{ + auto& realm = this->realm(); + // 1. Let format, unwrappingKey, algorithm, unwrappedKeyAlgorithm, extractable and usages, be the format, unwrappingKey, unwrapAlgorithm, + // unwrappedKeyAlgorithm, extractable and keyUsages parameters passed to the unwrapKey() method, respectively. + + // 2. Let wrappedKey be the result of getting a copy of the bytes held by the wrappedKey parameter passed to the unwrapKey() method. + auto real_wrapped_key = MUST(WebIDL::get_buffer_source_copy(*wrapped_key.get>()->raw_object())); + + StringView operation; + auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr { + // 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "unwrapKey". + auto normalized_algorithm_unwrap_key_or_error = normalize_an_algorithm(realm, algorithm, "unwrapKey"_string); + + // 4. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "decrypt". + // 5. If an error occurred, return a Promise rejected with normalizedAlgorithm. + if (normalized_algorithm_unwrap_key_or_error.is_error()) { + auto normalized_algorithm_decrypt_or_error = normalize_an_algorithm(realm, algorithm, "decrypt"_string); + if (normalized_algorithm_decrypt_or_error.is_error()) + return normalized_algorithm_decrypt_or_error.release_error(); + + operation = "decrypt"sv; + return normalized_algorithm_decrypt_or_error.release_value(); + } else { + operation = "unwrapKey"sv; + return normalized_algorithm_unwrap_key_or_error.release_value(); + } + }(); + if (normalized_algorithm_or_error.is_error()) + return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error()); + + auto normalized_algorithm = normalized_algorithm_or_error.release_value(); + + // 6. Let normalizedKeyAlgorithm be the result of normalizing an algorithm, with alg set to unwrappedKeyAlgorithm and op set to "importKey". + auto normalized_key_algorithm_or_error = normalize_an_algorithm(realm, unwrapped_key_algorithm, "importKey"_string); + if (normalized_key_algorithm_or_error.is_error()) { + // 7. If an error occurred, return a Promise rejected with normalizedKeyAlgorithm. + return WebIDL::create_rejected_promise_from_exception(realm, normalized_key_algorithm_or_error.release_error()); + } + + auto normalized_key_algorithm = normalized_key_algorithm_or_error.release_value(); + + // 8. Let promise be a new Promise. + auto promise = WebIDL::create_promise(realm); + + // 9. Return promise and perform the remaining steps in parallel. + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, unwrapping_key = unwrapping_key, real_wrapped_key = move(real_wrapped_key), operation, format, extractable, key_usages = move(key_usages), normalized_key_algorithm = move(normalized_key_algorithm)]() mutable -> void { + HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + // 10. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm. + + // 11. If the name member of normalizedAlgorithm is not equal to the name attribute of the [[algorithm]] internal slot + // of unwrappingKey then throw an InvalidAccessError. + if (normalized_algorithm.parameter->name != unwrapping_key->algorithm_name()) { + WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string)); + return; + } + + // 12. If the [[usages]] internal slot of unwrappingKey does not contain an entry that is "unwrapKey", then throw an InvalidAccessError. + if (!unwrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Unwrapkey)) { + WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support unwrapping keys"_string)); + return; + } + + auto key_or_error = [&]() -> WebIDL::ExceptionOr> { + // 13. If normalizedAlgorithm supports an unwrap key operation: + if (operation == "unwrapKey") { + // Let key be the result of performing the unwrap key operation specified by normalizedAlgorithm + // using algorithm, unwrappingKey as key and wrappedKey as ciphertext. + return normalized_algorithm.methods->unwrap_key(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key); + } + + // Otherwise, if normalizedAlgorithm supports a decrypt operation: + else if (operation == "decrypt") { + // Let key be the result of performing the decrypt operation specified by normalizedAlgorithm + // using algorithm, unwrappingKey as key and wrappedKey as ciphertext. + return normalized_algorithm.methods->decrypt(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key); + } + + // Otherwise: + else { + // throw a NotSupportedError. + return WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string); + } + }(); + if (key_or_error.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_or_error.release_error()).release_value().value()); + return; + } + + auto key = key_or_error.release_value(); + + Variant bytes; + + // 14. If format is equal to the strings "raw", "pkcs8", or "spki": + if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) { + // Set bytes be set to key. + bytes = key->buffer(); + } + + // If format is equal to the string "jwk": + else if (format == Bindings::KeyFormat::Jwk) { + // Let bytes be the result of executing the parse a JWK algorithm, with key as the data to be parsed. + auto maybe_parsed = Bindings::JsonWebKey::parse(realm, key->buffer()); + if (maybe_parsed.is_error()) { + WebIDL::reject_promise(realm, promise, maybe_parsed.release_error().release_value().value()); + return; + } + + bytes = maybe_parsed.release_value(); + } else { + VERIFY_NOT_REACHED(); + } + + // 15. Let result be the result of performing the import key operation specified by normalizedKeyAlgorithm + // using unwrappedKeyAlgorithm as algorithm, format, usages and extractable and with bytes as keyData. + auto result_or_error = normalized_key_algorithm.methods->import_key(*normalized_key_algorithm.parameter, format, bytes.downcast(), extractable, key_usages); + if (result_or_error.is_error()) { + WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value()); + return; + } + + auto result = result_or_error.release_value(); + + // 16. If the [[type]] internal slot of result is "secret" or "private" and usages is empty, then throw a SyntaxError. + if ((result->type() == Bindings::KeyType::Secret || result->type() == Bindings::KeyType::Private) && key_usages.is_empty()) { + WebIDL::reject_promise(realm, promise, WebIDL::SyntaxError::create(realm, "Usages must not be empty"_string)); + return; + } + + // 17. Set the [[extractable]] internal slot of result to extractable. + result->set_extractable(extractable); + + // 18. Set the [[usages]] internal slot of result to the normalized value of usages. + normalize_key_usages(key_usages); + result->set_usages(key_usages); + + // 19. Resolve promise with result. + WebIDL::resolve_promise(realm, promise, result); + })); + + return promise; +} + SupportedAlgorithmsMap& supported_algorithms_internal() { static SupportedAlgorithmsMap s_supported_algorithms; diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.h b/Libraries/LibWeb/Crypto/SubtleCrypto.h index f8ca8e032edaa..014e9b478d2e0 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.h +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.h @@ -40,6 +40,9 @@ class SubtleCrypto final : public Bindings::PlatformObject { JS::ThrowCompletionOr> import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector key_usages); JS::ThrowCompletionOr> export_key(Bindings::KeyFormat format, GC::Ref key); + JS::ThrowCompletionOr> wrap_key(Bindings::KeyFormat format, GC::Ref key, GC::Ref wrapping_key, AlgorithmIdentifier wrap_algorithm); + JS::ThrowCompletionOr> unwrap_key(Bindings::KeyFormat format, KeyDataType wrapped_key, GC::Ref unwrapping_key, AlgorithmIdentifier unwrap_algorithm, AlgorithmIdentifier unwrapped_key_algorithm, bool extractable, Vector key_usages); + private: explicit SubtleCrypto(JS::Realm&); virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.idl b/Libraries/LibWeb/Crypto/SubtleCrypto.idl index 2baa4db86e616..bf47ba840d180 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.idl +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.idl @@ -58,6 +58,6 @@ interface SubtleCrypto { Promise importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence keyUsages); Promise exportKey(KeyFormat format, CryptoKey key); - [FIXME] Promise wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm); - [FIXME] Promise unwrapKey(KeyFormat format, BufferSource wrappedKey, CryptoKey unwrappingKey, AlgorithmIdentifier unwrapAlgorithm, AlgorithmIdentifier unwrappedKeyAlgorithm, boolean extractable, sequence keyUsages); + Promise wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm); + Promise unwrapKey(KeyFormat format, BufferSource wrappedKey, CryptoKey unwrappingKey, AlgorithmIdentifier unwrapAlgorithm, AlgorithmIdentifier unwrappedKeyAlgorithm, boolean extractable, sequence keyUsages); }; diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt new file mode 100644 index 0000000000000..f08f7c144ee98 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt @@ -0,0 +1,250 @@ +Harness status: OK + +Found 244 tests + +173 Pass +71 Fail +Pass setup +Pass Can wrap and unwrap RSA-OAEP public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDSA public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap ECDSA public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap ECDSA private key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap ECDSA private key non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap ECDH public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDH private key keys using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap ECDH private key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap ECDH private key non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap Ed25519 public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap Ed25519 public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap Ed25519 private key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap Ed25519 private key non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X25519 public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap X25519 public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X25519 private key keys using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap X25519 private key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap X25519 private key non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X448 public key keys using spki and RSA-OAEP +Pass Can wrap and unwrap X448 public key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X448 private key keys using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and RSA-OAEP +Pass Can wrap and unwrap X448 private key keys using jwk and RSA-OAEP +Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap X448 private key non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-CTR keys using raw and RSA-OAEP +Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap AES-CTR keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap AES-CTR non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-CBC keys using raw and RSA-OAEP +Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap AES-CBC keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap AES-CBC non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-GCM keys using raw and RSA-OAEP +Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap AES-GCM keys using jwk and RSA-OAEP +Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP +Fail Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap HMAC keys using raw and RSA-OAEP +Pass Can wrap and unwrap HMAC keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap HMAC keys using jwk and RSA-OAEP +Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap HMAC non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap RSA-OAEP public key keys using spki and AES-CTR +Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-CTR +Pass Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-CTR +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDSA public key keys using spki and AES-CTR +Pass Can wrap and unwrap ECDSA public key keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap ECDSA private key keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap ECDSA private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDH public key keys using spki and AES-CTR +Pass Can wrap and unwrap ECDH public key keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDH private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap ECDH private key keys using jwk and AES-CTR +Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap ECDH private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap Ed25519 public key keys using spki and AES-CTR +Pass Can wrap and unwrap Ed25519 public key keys using jwk and AES-CTR +Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap Ed25519 private key keys using jwk and AES-CTR +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap Ed25519 private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap X25519 public key keys using spki and AES-CTR +Pass Can wrap and unwrap X25519 public key keys using jwk and AES-CTR +Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap X25519 private key keys using jwk and AES-CTR +Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap X25519 private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap X448 public key keys using spki and AES-CTR +Pass Can wrap and unwrap X448 public key keys using jwk and AES-CTR +Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-CTR +Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-CTR +Pass Can wrap and unwrap X448 private key keys using jwk and AES-CTR +Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-CTR +Pass Can unwrap X448 private key non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-CTR keys using raw and AES-CTR +Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap AES-CTR keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-CTR +Pass Can unwrap AES-CTR non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-CBC keys using raw and AES-CTR +Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CTR +Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-GCM keys using raw and AES-CTR +Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CTR +Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR +Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap HMAC keys using raw and AES-CTR +Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap HMAC keys using jwk and AES-CTR +Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-CTR +Pass Can unwrap HMAC non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap RSA-OAEP public key keys using spki and AES-CBC +Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-CBC +Pass Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-CBC +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDSA public key keys using spki and AES-CBC +Pass Can wrap and unwrap ECDSA public key keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap ECDSA private key keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap ECDSA private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDH public key keys using spki and AES-CBC +Pass Can wrap and unwrap ECDH public key keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDH private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap ECDH private key keys using jwk and AES-CBC +Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap ECDH private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap Ed25519 public key keys using spki and AES-CBC +Pass Can wrap and unwrap Ed25519 public key keys using jwk and AES-CBC +Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap Ed25519 private key keys using jwk and AES-CBC +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap Ed25519 private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap X25519 public key keys using spki and AES-CBC +Pass Can wrap and unwrap X25519 public key keys using jwk and AES-CBC +Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap X25519 private key keys using jwk and AES-CBC +Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap X25519 private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap X448 public key keys using spki and AES-CBC +Pass Can wrap and unwrap X448 public key keys using jwk and AES-CBC +Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-CBC +Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-CBC +Pass Can wrap and unwrap X448 private key keys using jwk and AES-CBC +Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-CBC +Pass Can unwrap X448 private key non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-CTR keys using raw and AES-CBC +Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap AES-CTR keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-CBC +Pass Can unwrap AES-CTR non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-CBC keys using raw and AES-CBC +Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CBC +Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-GCM keys using raw and AES-CBC +Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CBC +Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC +Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap HMAC keys using raw and AES-CBC +Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap HMAC keys using jwk and AES-CBC +Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-CBC +Pass Can unwrap HMAC non-extractable keys using jwk and AES-CBC +Fail Can wrap and unwrap RSA-OAEP public key keys using spki and AES-GCM +Fail Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-GCM +Fail Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-GCM +Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDSA public key keys using spki and AES-GCM +Fail Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDH public key keys using spki and AES-GCM +Fail Can wrap and unwrap ECDH public key keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDH private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap ECDH private key keys using jwk and AES-GCM +Fail Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap ECDH private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap Ed25519 public key keys using spki and AES-GCM +Fail Can wrap and unwrap Ed25519 public key keys using jwk and AES-GCM +Fail Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap Ed25519 private key keys using jwk and AES-GCM +Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap Ed25519 private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap X25519 public key keys using spki and AES-GCM +Fail Can wrap and unwrap X25519 public key keys using jwk and AES-GCM +Fail Can wrap and unwrap X25519 private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap X25519 private key keys using jwk and AES-GCM +Fail Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap X25519 private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap X448 public key keys using spki and AES-GCM +Fail Can wrap and unwrap X448 public key keys using jwk and AES-GCM +Fail Can wrap and unwrap X448 private key keys using pkcs8 and AES-GCM +Fail Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-GCM +Fail Can wrap and unwrap X448 private key keys using jwk and AES-GCM +Fail Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-GCM +Fail Can unwrap X448 private key non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-CTR keys using raw and AES-GCM +Fail Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-GCM +Fail Can wrap and unwrap AES-CTR keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-GCM +Fail Can unwrap AES-CTR non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-CBC keys using raw and AES-GCM +Fail Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-GCM +Fail Can wrap and unwrap AES-CBC keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-GCM +Fail Can unwrap AES-CBC non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-GCM keys using raw and AES-GCM +Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-GCM +Fail Can wrap and unwrap AES-GCM keys using jwk and AES-GCM +Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-GCM +Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-GCM +Fail Can wrap and unwrap HMAC keys using raw and AES-GCM +Fail Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM +Fail Can wrap and unwrap HMAC keys using jwk and AES-GCM +Fail Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM +Fail Can unwrap HMAC non-extractable keys using jwk and AES-GCM \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.html new file mode 100644 index 0000000000000..326148ab90810 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.html @@ -0,0 +1,17 @@ + + +WebCryptoAPI: wrapKey() and unwrapKey() + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js new file mode 100644 index 0000000000000..9dd837b3bf60a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js @@ -0,0 +1,536 @@ +// META: title=WebCryptoAPI: wrapKey() and unwrapKey() +// META: timeout=long +// META: script=../util/helpers.js +// META: script=wrapKey_unwrapKey_vectors.js + +// Tests for wrapKey and unwrapKey round tripping + + var subtle = self.crypto.subtle; + + var wrappers = {}; // Things we wrap (and upwrap) keys with + var keys = {}; // Things to wrap and unwrap + + // There are five algorithms that can be used for wrapKey/unwrapKey. + // Generate one key with typical parameters for each kind. + // + // Note: we don't need cryptographically strong parameters for things + // like IV - just any legal value will do. + var wrappingKeysParameters = [ + { + name: "RSA-OAEP", + importParameters: {name: "RSA-OAEP", hash: "SHA-256"}, + wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)} + }, + { + name: "AES-CTR", + importParameters: {name: "AES-CTR", length: 128}, + wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), length: 64} + }, + { + name: "AES-CBC", + importParameters: {name: "AES-CBC", length: 128}, + wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)} + }, + { + name: "AES-GCM", + importParameters: {name: "AES-GCM", length: 128}, + wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 128} + }, + { + name: "AES-KW", + importParameters: {name: "AES-KW", length: 128}, + wrapParameters: {name: "AES-KW"} + } + ]; + + var keysToWrapParameters = [ + {algorithm: {name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "RSA-PSS", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "RSA-OAEP", hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsages: ["encrypt"]}, + {algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["deriveBits"], publicUsages: []}, + {algorithm: {name: "Ed25519" }, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "Ed448" }, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "X25519" }, privateUsages: ["deriveBits"], publicUsages: []}, + {algorithm: {name: "X448" }, privateUsages: ["deriveBits"], publicUsages: []}, + {algorithm: {name: "AES-CTR", length: 128}, usages: ["encrypt", "decrypt"]}, + {algorithm: {name: "AES-CBC", length: 128}, usages: ["encrypt", "decrypt"]}, + {algorithm: {name: "AES-GCM", length: 128}, usages: ["encrypt", "decrypt"]}, + {algorithm: {name: "AES-KW", length: 128}, usages: ["wrapKey", "unwrapKey"]}, + {algorithm: {name: "HMAC", length: 128, hash: "SHA-256"}, usages: ["sign", "verify"]} + ]; + + // Import all the keys needed, then iterate over all combinations + // to test wrapping and unwrapping. + promise_test(function() { + return Promise.all([importWrappingKeys(), importKeysToWrap()]) + .then(function(results) { + wrappingKeysParameters.filter((param) => Object.keys(wrappers).includes(param.name)).forEach(function(wrapperParam) { + var wrapper = wrappers[wrapperParam.name]; + keysToWrapParameters.filter((param) => Object.keys(keys).includes(param.algorithm.name)).forEach(function(toWrapParam) { + var keyData = keys[toWrapParam.algorithm.name]; + ["raw", "spki", "pkcs8"].filter((fmt) => Object.keys(keyData).includes(fmt)).forEach(function(keyDataFormat) { + var toWrap = keyData[keyDataFormat]; + [keyDataFormat, "jwk"].forEach(function(format) { + if (wrappingIsPossible(toWrap.originalExport[format], wrapper.parameters.name)) { + testWrapping(wrapper, toWrap, format); + if (canCompareNonExtractableKeys(toWrap.key)) { + testWrappingNonExtractable(wrapper, toWrap, format); + if (format === "jwk") { + testWrappingNonExtractableAsExtractable(wrapper, toWrap); + } + } + } + }); + }); + }); + }); + return Promise.resolve("setup done"); + }, function(err) { + return Promise.reject("setup failed: " + err.name + ': "' + err.message + '"'); + }); + }, "setup"); + + function importWrappingKeys() { + // Using allSettled to skip unsupported test cases. + var promises = []; + wrappingKeysParameters.forEach(function(params) { + if (params.name === "RSA-OAEP") { // we have a key pair, not just a key + var algorithm = {name: "RSA-OAEP", hash: "SHA-256"}; + wrappers[params.name] = {wrappingKey: undefined, unwrappingKey: undefined, parameters: params}; + promises.push(subtle.importKey("spki", wrappingKeyData["RSA"].spki, algorithm, true, ["wrapKey"]) + .then(function(key) { + wrappers["RSA-OAEP"].wrappingKey = key; + })); + promises.push(subtle.importKey("pkcs8", wrappingKeyData["RSA"].pkcs8, algorithm, true, ["unwrapKey"]) + .then(function(key) { + wrappers["RSA-OAEP"].unwrappingKey = key; + })); + } else { + var algorithm = {name: params.name}; + promises.push(subtle.importKey("raw", wrappingKeyData["SYMMETRIC"].raw, algorithm, true, ["wrapKey", "unwrapKey"]) + .then(function(key) { + wrappers[params.name] = {wrappingKey: key, unwrappingKey: key, parameters: params}; + })); + } + }); + // Using allSettled to skip unsupported test cases. + return Promise.allSettled(promises); + } + + async function importAndExport(format, keyData, algorithm, keyUsages, keyType) { + var importedKey; + try { + importedKey = await subtle.importKey(format, keyData, algorithm, true, keyUsages); + keys[algorithm.name][format] = { name: algorithm.name + " " + keyType, algorithm: algorithm, usages: keyUsages, key: importedKey, originalExport: {} }; + } catch (err) { + delete keys[algorithm.name][format]; + throw("Error importing " + algorithm.name + " " + keyType + " key in '" + format + "' -" + err.name + ': "' + err.message + '"'); + }; + try { + var exportedKey = await subtle.exportKey(format, importedKey); + keys[algorithm.name][format].originalExport[format] = exportedKey; + } catch (err) { + delete keys[algorithm.name][format]; + throw("Error exporting " + algorithm.name + " '" + format + "' key -" + err.name + ': "' + err.message + '"'); + }; + try { + var jwkExportedKey = await subtle.exportKey("jwk", importedKey); + keys[algorithm.name][format].originalExport["jwk"] = jwkExportedKey; + } catch (err) { + delete keys[algorithm.name][format]; + throw("Error exporting " + algorithm.name + " '" + format + "' key to 'jwk' -" + err.name + ': "' + err.message + '"'); + }; + } + + function importKeysToWrap() { + var promises = []; + keysToWrapParameters.forEach(function(params) { + if ("publicUsages" in params) { + keys[params.algorithm.name] = {}; + var keyData = toWrapKeyDataFromAlg(params.algorithm.name); + promises.push(importAndExport("spki", keyData.spki, params.algorithm, params.publicUsages, "public key ")); + promises.push(importAndExport("pkcs8", keyData.pkcs8, params.algorithm, params.privateUsages, "private key ")); + } else { + keys[params.algorithm.name] = {}; + promises.push(importAndExport("raw", toWrapKeyData["SYMMETRIC"].raw, params.algorithm, params.usages, "")); + } + }); + // Using allSettled to skip unsupported test cases. + return Promise.allSettled(promises); + } + + // Can we successfully "round-trip" (wrap, then unwrap, a key)? + function testWrapping(wrapper, toWrap, fmt) { + promise_test(async() => { + try { + var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters); + var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); + assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, true, toWrap.usages, toWrap.key.type); + var roundTripExport = await subtle.exportKey(fmt, unwrappedResult); + assert_true(equalExport(toWrap.originalExport[fmt], roundTripExport), "Post-wrap export matches original export"); + } catch (err) { + if (err instanceof AssertionError) { + throw err; + } + assert_unreached("Round trip for extractable key threw an error - " + err.name + ': "' + err.message + '"'); + } + }, "Can wrap and unwrap " + toWrap.name + "keys using " + fmt + " and " + wrapper.parameters.name); + } + + function testWrappingNonExtractable(wrapper, toWrap, fmt) { + promise_test(async() => { + try { + var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters); + var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); + assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, false, toWrap.usages, toWrap.key.type); + var result = await equalKeys(toWrap.key, unwrappedResult); + assert_true(result, "Unwrapped key matches original"); + } catch (err) { + if (err instanceof AssertionError) { + throw err; + } + assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"'); + }; + }, "Can wrap and unwrap " + toWrap.name + "keys as non-extractable using " + fmt + " and " + wrapper.parameters.name); + } + + function testWrappingNonExtractableAsExtractable(wrapper, toWrap) { + promise_test(async() => { + var wrappedKey; + try { + var wrappedResult = await wrapAsNonExtractableJwk(toWrap.key,wrapper); + wrappedKey = wrappedResult; + var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); + assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable"); + var result = await equalKeys(toWrap.key,unwrappedResult); + assert_true(result, "Unwrapped key matches original"); + } catch (err) { + if (err instanceof AssertionError) { + throw err; + } + assert_unreached("Round trip for non-extractable key threw an error - " + err.name + ': "' + err.message + '"'); + }; + try { + var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); + assert_unreached("Unwrapping a non-extractable JWK as extractable should fail"); + } catch (err) { + if (err instanceof AssertionError) { + throw err; + } + assert_equals(err.name, "DataError", "Unwrapping a non-extractable JWK as extractable fails with DataError"); + } + }, "Can unwrap " + toWrap.name + "non-extractable keys using jwk and " + wrapper.parameters.name); + } + + // Implement key wrapping by hand to wrap a key as non-extractable JWK + async function wrapAsNonExtractableJwk(key, wrapper) { + var wrappingKey = wrapper.wrappingKey, + encryptKey; + + var jwkWrappingKey = await subtle.exportKey("jwk",wrappingKey); + // Update the key generation parameters to work as key import parameters + var params = Object.create(wrapper.parameters.importParameters); + if(params.name === "AES-KW") { + params.name = "AES-CBC"; + jwkWrappingKey.alg = "A"+params.length+"CBC"; + } else if (params.name === "RSA-OAEP") { + params.modulusLength = undefined; + params.publicExponent = undefined; + } + jwkWrappingKey.key_ops = ["encrypt"]; + var importedWrappingKey = await subtle.importKey("jwk", jwkWrappingKey, params, true, ["encrypt"]); + encryptKey = importedWrappingKey; + var exportedKey = await subtle.exportKey("jwk",key); + exportedKey.ext = false; + var jwk = JSON.stringify(exportedKey) + var result; + if (wrappingKey.algorithm.name === "AES-KW") { + result = await aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk.length%8 ? 8-jwk.length%8 : 0) + "}")); + } else { + result = await subtle.encrypt(wrapper.parameters.wrapParameters,encryptKey,str2ab(jwk)); + } + return result; + } + + + // RSA-OAEP can only wrap relatively small payloads. AES-KW can only + // wrap payloads a multiple of 8 bytes long. + function wrappingIsPossible(exportedKey, algorithmName) { + if ("byteLength" in exportedKey && algorithmName === "AES-KW") { + return exportedKey.byteLength % 8 === 0; + } + + if ("byteLength" in exportedKey && algorithmName === "RSA-OAEP") { + // RSA-OAEP can only encrypt payloads with lengths shorter + // than modulusLength - 2*hashLength - 1 bytes long. For + // a 4096 bit modulus and SHA-256, that comes to + // 4096/8 - 2*(256/8) - 1 = 512 - 2*32 - 1 = 447 bytes. + return exportedKey.byteLength <= 446; + } + + if ("kty" in exportedKey && algorithmName === "AES-KW") { + return JSON.stringify(exportedKey).length % 8 == 0; + } + + if ("kty" in exportedKey && algorithmName === "RSA-OAEP") { + return JSON.stringify(exportedKey).length <= 478; + } + + return true; + } + + + // Helper methods follow: + + // Are two exported keys equal + function equalExport(originalExport, roundTripExport) { + if ("byteLength" in originalExport) { + return equalBuffers(originalExport, roundTripExport); + } else { + return equalJwk(originalExport, roundTripExport); + } + } + + // Are two array buffers the same? + function equalBuffers(a, b) { + if (a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + for (var i=0; i x); + } else if (signParams) { + var verifyKey; + var jwkExpectedKey = await subtle.exportKey("jwk",expected); + if (expected.algorithm.name === "RSA-PSS" || expected.algorithm.name === "RSASSA-PKCS1-v1_5") { + ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; }); + } + if (expected.algorithm.name === "ECDSA" || expected.algorithm.name.startsWith("Ed")) { + delete jwkExpectedKey["d"]; + } + jwkExpectedKey.key_ops = ["verify"]; + var expectedVerifyKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["verify"]); + verifyKey = expectedVerifyKey; + var signature = await subtle.sign(signParams, got, new Uint8Array(32)); + var result = await subtle.verify(signParams, verifyKey, signature, new Uint8Array(32)); + return result; + } else if (wrapParams) { + var aKeyToWrap, wrappedWithExpected; + var key = await subtle.importKey("raw",new Uint8Array(16), "AES-CBC", true, ["encrypt"]) + aKeyToWrap = key; + var wrapResult = await subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams); + wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).values()); + wrapResult = await subtle.wrapKey("raw", aKeyToWrap, got, wrapParams); + var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values()); + return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]); + } else if (deriveParams) { + var expectedDerivedBits; + var key = await subtle.generateKey(expected.algorithm, true, ['deriveBits']); + deriveParams.public = key.publicKey; + var result = await subtle.deriveBits(deriveParams, expected, 128); + expectedDerivedBits = Array.from((new Uint8Array(result)).values()); + result = await subtle.deriveBits(deriveParams, got, 128); + var gotDerivedBits = Array.from((new Uint8Array(result)).values()); + return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i]); + } + } + + // Raw AES encryption + async function aes(k, p) { + const ciphertext = await subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, k, p); + return ciphertext.slice(0, 16); + } + + // AES Key Wrap + async function aeskw(key, data) { + if (data.byteLength % 8 !== 0) { + throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length"); + } + + var A = Uint8Array.from([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0, 0, 0, 0, 0, 0, 0, 0]), + Av = new DataView(A.buffer), + R = [], + n = data.byteLength / 8; + + for(var i = 0; i Date: Mon, 9 Dec 2024 12:53:47 +0000 Subject: [PATCH 202/237] LibWeb: Collapse selection when selecting text inside a text control --- Libraries/LibWeb/HTML/FormAssociatedElement.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index 6927d7812ae2c..f5ab649075db3 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include namespace Web::HTML { @@ -691,6 +693,8 @@ void FormAssociatedTextControlElement::select_all() void FormAssociatedTextControlElement::set_selection_anchor(GC::Ref anchor_node, size_t anchor_offset) { + auto editing_host_manager = form_associated_element_to_html_element().document().editing_host_manager(); + editing_host_manager->set_selection_anchor(anchor_node, anchor_offset); auto text_node = form_associated_element_to_text_node(); if (!text_node || anchor_node != text_node) return; @@ -700,6 +704,8 @@ void FormAssociatedTextControlElement::set_selection_anchor(GC::Ref a void FormAssociatedTextControlElement::set_selection_focus(GC::Ref focus_node, size_t focus_offset) { + auto editing_host_manager = form_associated_element_to_html_element().document().editing_host_manager(); + editing_host_manager->set_selection_focus(focus_node, focus_offset); auto text_node = form_associated_element_to_text_node(); if (!text_node || focus_node != text_node) return; From b263cd11f708492c2b3cbfe1552b97ad3a7a08e2 Mon Sep 17 00:00:00 2001 From: Glenn Skrzypczak Date: Sun, 24 Nov 2024 20:37:13 +0100 Subject: [PATCH 203/237] LibWeb/Fetch: Deserialize abort reason Deserialize the fetch controllers abort reason according to the spec. Currently fetch does not support a scenario where this actually happens. --- Libraries/LibWeb/Fetch/FetchMethod.cpp | 20 +++++----- .../Fetch/Infrastructure/FetchController.cpp | 37 ++++++++++++++++++- .../Fetch/Infrastructure/FetchController.h | 21 ++++++++++- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Fetch/FetchMethod.cpp b/Libraries/LibWeb/Fetch/FetchMethod.cpp index 2d528872543b2..38a7c65eca408 100644 --- a/Libraries/LibWeb/Fetch/FetchMethod.cpp +++ b/Libraries/LibWeb/Fetch/FetchMethod.cpp @@ -73,14 +73,14 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit auto locally_aborted = Fetching::RefCountedFlag::create(false); // 10. Let controller be null. - GC::Ptr controller; + auto controller_holder = Infrastructure::FetchControllerHolder::create(vm); // NOTE: Step 11 is done out of order so that the controller is non-null when we capture the GCPtr by copy in the abort algorithm lambda. // This is not observable, AFAICT. // 12. Set controller to the result of calling fetch given request and processResponse given response being these // steps: - auto process_response = [locally_aborted, promise_capability, request, response_object, &relevant_realm](GC::Ref response) mutable { + auto process_response = [locally_aborted, promise_capability, request, response_object, controller_holder, &relevant_realm](GC::Ref response) mutable { // 1. If locallyAborted is true, then abort these steps. if (locally_aborted->value()) return; @@ -90,9 +90,9 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit // 2. If response’s aborted flag is set, then: if (response->aborted()) { - // FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s - // serialized abort reason and relevantRealm. - auto deserialized_error = JS::js_undefined(); + // 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s + // serialized abort reason and relevantRealm. + auto deserialized_error = controller_holder->controller()->deserialize_a_serialized_abort_reason(relevant_realm); // 2. Abort the fetch() call with p, request, responseObject, and deserializedError. abort_fetch(relevant_realm, promise_capability, request, response_object, deserialized_error); @@ -115,7 +115,7 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit // 5. Resolve p with responseObject. WebIDL::resolve_promise(relevant_realm, promise_capability, response_object); }; - controller = MUST(Fetching::fetch( + controller_holder->set_controller(MUST(Fetching::fetch( realm, request, Infrastructure::FetchAlgorithms::create(vm, @@ -126,20 +126,20 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit .process_response = move(process_response), .process_response_end_of_body = {}, .process_response_consume_body = {}, - }))); + })))); // 11. Add the following abort steps to requestObject’s signal: - request_object->signal()->add_abort_algorithm([locally_aborted, request, controller, promise_capability, request_object, response_object, &relevant_realm] { + request_object->signal()->add_abort_algorithm([locally_aborted, request, controller_holder, promise_capability, request_object, response_object, &relevant_realm] { dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called"); // 1. Set locallyAborted to true. locally_aborted->set_value(true); // 2. Assert: controller is non-null. - VERIFY(controller); + VERIFY(controller_holder->controller()); // 3. Abort controller with requestObject’s signal’s abort reason. - controller->abort(relevant_realm, request_object->signal()->reason()); + controller_holder->controller()->abort(relevant_realm, request_object->signal()->reason()); // AD-HOC: An execution context is required for Promise functions. HTML::TemporaryExecutionContext execution_context { relevant_realm }; diff --git a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp index 26f0299c61aaa..7cc5d56e7de93 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp @@ -96,7 +96,27 @@ void FetchController::abort(JS::Realm& realm, Optional error) m_serialized_abort_reason = structured_serialize(realm.vm(), error.value(), fallback_error); } -// FIXME: https://fetch.spec.whatwg.org/#deserialize-a-serialized-abort-reason +// https://fetch.spec.whatwg.org/#deserialize-a-serialized-abort-reason +JS::Value FetchController::deserialize_a_serialized_abort_reason(JS::Realm& realm) +{ + // 1. Let fallbackError be an "AbortError" DOMException. + auto fallback_error = WebIDL::AbortError::create(realm, "Fetch was aborted"_string); + + // 2. Let deserializedError be fallbackError. + JS::Value deserialized_error = fallback_error; + + // 3. If abortReason is non-null, then set deserializedError to StructuredDeserialize(abortReason, realm). + // If that threw an exception or returned undefined, then set deserializedError to fallbackError. + if (m_serialized_abort_reason.has_value()) { + auto deserialized_error_or_exception = HTML::structured_deserialize(realm.vm(), m_serialized_abort_reason.value(), realm, {}); + if (!deserialized_error_or_exception.is_exception() && !deserialized_error_or_exception.value().is_undefined()) { + deserialized_error = deserialized_error_or_exception.value(); + } + } + + // 4. Return deserializedError. + return deserialized_error; +} // https://fetch.spec.whatwg.org/#fetch-controller-terminate void FetchController::terminate() @@ -137,4 +157,19 @@ void FetchController::fetch_task_complete(u64 fetch_task_id) m_ongoing_fetch_tasks.remove(fetch_task_id); } +GC_DEFINE_ALLOCATOR(FetchControllerHolder); + +FetchControllerHolder::FetchControllerHolder() = default; + +GC::Ref FetchControllerHolder::create(JS::VM& vm) +{ + return vm.heap().allocate(); +} + +void FetchControllerHolder::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_controller); +} + } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h index cbefaa2ffd8dd..3506789a75e99 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h @@ -44,6 +44,7 @@ class FetchController : public JS::Cell { void process_next_manual_redirect() const; [[nodiscard]] GC::Ref extract_full_timing_info() const; void abort(JS::Realm&, Optional); + JS::Value deserialize_a_serialized_abort_reason(JS::Realm&); void terminate(); void set_fetch_params(Badge, GC::Ref fetch_params) { m_fetch_params = fetch_params; } @@ -77,7 +78,7 @@ class FetchController : public JS::Cell { // https://fetch.spec.whatwg.org/#fetch-controller-report-timing-steps // serialized abort reason (default null) // Null or a Record (result of StructuredSerialize). - HTML::SerializationRecord m_serialized_abort_reason; + Optional m_serialized_abort_reason; // https://fetch.spec.whatwg.org/#fetch-controller-next-manual-redirect-steps // next manual redirect steps (default null) @@ -90,4 +91,22 @@ class FetchController : public JS::Cell { u64 m_next_fetch_task_id { 0 }; }; +class FetchControllerHolder : public JS::Cell { + GC_CELL(FetchControllerHolder, JS::Cell); + GC_DECLARE_ALLOCATOR(FetchControllerHolder); + +public: + static GC::Ref create(JS::VM&); + + [[nodiscard]] GC::Ptr const& controller() const { return m_controller; } + void set_controller(GC::Ref controller) { m_controller = controller; } + +private: + FetchControllerHolder(); + + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ptr m_controller; +}; + } From e18fb7fc93023e67653a2e9ec34f497035fbd51c Mon Sep 17 00:00:00 2001 From: Glenn Skrzypczak Date: Sun, 24 Nov 2024 20:39:32 +0100 Subject: [PATCH 204/237] LibWeb/Fetch: Handle streams on abort When aborting fetch, the request stream now gets closed and the response stream errors. --- Libraries/LibWeb/Fetch/FetchMethod.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/LibWeb/Fetch/FetchMethod.cpp b/Libraries/LibWeb/Fetch/FetchMethod.cpp index 38a7c65eca408..7a95e4d45f2eb 100644 --- a/Libraries/LibWeb/Fetch/FetchMethod.cpp +++ b/Libraries/LibWeb/Fetch/FetchMethod.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -163,8 +164,8 @@ void abort_fetch(JS::Realm& realm, WebIDL::Promise const& promise, GC::Refbody().get_pointer>(); body != nullptr && (*body)->stream()->is_readable()) { - // TODO: Implement cancelling streams - (void)error; + // NOTE: Cancel here is different than the cancel method of stream and refers to https://streams.spec.whatwg.org/#readablestream-cancel + Streams::readable_stream_cancel((*body)->stream(), error); } // 3. If responseObject is null, then return. @@ -178,8 +179,7 @@ void abort_fetch(JS::Realm& realm, WebIDL::Promise const& promise, GC::Refbody()) { auto stream = response->body()->stream(); if (stream->is_readable()) { - // TODO: Implement erroring streams - (void)error; + stream->error(error); } } } From 3167d4f06b560821908ae4ed77470ec25296536b Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 21:34:59 +0100 Subject: [PATCH 205/237] LibCrypto: Move `GHash` hashing routine to separate function This allows for the function to be used outside `GHash`. In particular, it'll be used for IV preparation in AES-GCM. --- Libraries/LibCrypto/Authentication/GHash.cpp | 45 ++++++++++---------- Libraries/LibCrypto/Authentication/GHash.h | 1 + 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Libraries/LibCrypto/Authentication/GHash.cpp b/Libraries/LibCrypto/Authentication/GHash.cpp index a40a6026d9758..0eb8d31716469 100644 --- a/Libraries/LibCrypto/Authentication/GHash.cpp +++ b/Libraries/LibCrypto/Authentication/GHash.cpp @@ -27,35 +27,36 @@ static void to_u8s(u8* b, u32 const* w) namespace Crypto::Authentication { -GHash::TagType GHash::process(ReadonlyBytes aad, ReadonlyBytes cipher) +void GHash::process_one(u32 (&tag)[4], ReadonlyBytes buf) const { - u32 tag[4] { 0, 0, 0, 0 }; - - auto transform_one = [&](auto& buf) { - size_t i = 0; - for (; i < buf.size(); i += 16) { - if (i + 16 <= buf.size()) { - for (auto j = 0; j < 4; ++j) { - tag[j] ^= to_u32(buf.offset(i + j * 4)); - } - galois_multiply(tag, m_key, tag); + size_t i = 0; + for (; i < buf.size(); i += 16) { + if (i + 16 <= buf.size()) { + for (auto j = 0; j < 4; ++j) { + tag[j] ^= to_u32(buf.offset(i + j * 4)); } + galois_multiply(tag, m_key, tag); } + } - if (i > buf.size()) { - u8 buffer[16] = {}; - Bytes buffer_bytes { buffer, 16 }; - buf.slice(i - 16).copy_to(buffer_bytes); + if (i > buf.size()) { + u8 buffer[16] = {}; + Bytes buffer_bytes { buffer, 16 }; + (void)buf.slice(i - 16).copy_to(buffer_bytes); - for (auto j = 0; j < 4; ++j) { - tag[j] ^= to_u32(buffer_bytes.offset(j * 4)); - } - galois_multiply(tag, m_key, tag); + for (auto j = 0; j < 4; ++j) { + tag[j] ^= to_u32(buffer_bytes.offset(j * 4)); } - }; + galois_multiply(tag, m_key, tag); + } +} + +GHash::TagType GHash::process(ReadonlyBytes aad, ReadonlyBytes cipher) +{ + u32 tag[4] { 0, 0, 0, 0 }; - transform_one(aad); - transform_one(cipher); + process_one(tag, aad); + process_one(tag, cipher); auto aad_bits = 8 * (u64)aad.size(); auto cipher_bits = 8 * (u64)cipher.size(); diff --git a/Libraries/LibCrypto/Authentication/GHash.h b/Libraries/LibCrypto/Authentication/GHash.h index c2c4d8795c12c..6eab8ee4bc7f1 100644 --- a/Libraries/LibCrypto/Authentication/GHash.h +++ b/Libraries/LibCrypto/Authentication/GHash.h @@ -48,6 +48,7 @@ class GHash final { return "GHash"; } + void process_one(u32 (&tag)[4], ReadonlyBytes buf) const; TagType process(ReadonlyBytes aad, ReadonlyBytes cipher); private: From 1ae28324bd2c8aeb7d8b4d84622005a036c8b944 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 21:41:06 +0100 Subject: [PATCH 206/237] LibCrypto: Accept correct IV sizes for AES-GCM AES-GCM should accept 96-bits keys as is. Any other key should be preprocessed with GHASH. --- Libraries/LibCrypto/Cipher/Mode/GCM.h | 58 ++++++++++++++++++--------- Libraries/LibTLS/Record.cpp | 14 ++----- Tests/LibCrypto/TestAES.cpp | 16 ++++---- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/Libraries/LibCrypto/Cipher/Mode/GCM.h b/Libraries/LibCrypto/Cipher/Mode/GCM.h index 20ad8e390a3f0..211866673786a 100644 --- a/Libraries/LibCrypto/Cipher/Mode/GCM.h +++ b/Libraries/LibCrypto/Cipher/Mode/GCM.h @@ -67,19 +67,44 @@ class GCM : public CTR { encrypt(in, out, ivec); } - void encrypt(ReadonlyBytes in, Bytes out, ReadonlyBytes iv_in, ReadonlyBytes aad, Bytes tag) + ByteBuffer process_iv(ReadonlyBytes iv_in) { - auto iv_buf_result = ByteBuffer::copy(iv_in); - // Not enough memory to figure out :shrug: - if (iv_buf_result.is_error()) { - dbgln("GCM::encrypt: Not enough memory to allocate {} bytes for IV", iv_in.size()); - return; + if (iv_in.size() == 12) { + auto buf = MUST(ByteBuffer::create_zeroed(16)); + buf.overwrite(0, iv_in.data(), iv_in.size()); + + // Increment the IV for block 0 + auto iv = buf.bytes(); + CTR::increment(iv); + + return buf; } - auto iv = iv_buf_result.value().bytes(); + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + // Otherwise, the IV is padded with the minimum number of '0' bits, possibly none, + // so that the length of the resulting string is a multiple of 128 bits (the block size); + // this string in turn is appended with 64 additional '0' bits, followed by + // the 64-bit representation of the length of the IV, and the GHASH function + // is applied to the resulting string to form the precounter block. + auto iv_pad = iv_in.size() % 16 == 0 ? 0 : 16 - (iv_in.size() % 16); + auto data = MUST(ByteBuffer::create_zeroed(iv_in.size() + iv_pad + 8 + 8)); + data.overwrite(0, iv_in.data(), iv_in.size()); + ByteReader::store(data.data() + iv_in.size() + iv_pad + 8, AK::convert_between_host_and_big_endian(iv_in.size() * 8)); + + u32 out[4] { 0, 0, 0, 0 }; + m_ghash->process_one(out, data); + + auto buf = MUST(ByteBuffer::create_uninitialized(16)); + for (size_t i = 0; i < 4; ++i) + ByteReader::store(buf.data() + (i * 4), AK::convert_between_host_and_big_endian(out[i])); + return buf; + } + + void encrypt(ReadonlyBytes in, Bytes out, ReadonlyBytes iv_in, ReadonlyBytes aad, Bytes tag) + { + auto iv_buf = process_iv(iv_in); + auto iv = iv_buf.bytes(); - // Increment the IV for block 0 - CTR::increment(iv); typename T::BlockType block0; block0.overwrite(iv); this->cipher().encrypt_block(block0, block0); @@ -94,20 +119,14 @@ class GCM : public CTR { auto auth_tag = m_ghash->process(aad, out); block0.apply_initialization_vector({ auth_tag.data, array_size(auth_tag.data) }); - block0.bytes().copy_to(tag); + (void)block0.bytes().copy_trimmed_to(tag); } VerificationConsistency decrypt(ReadonlyBytes in, Bytes out, ReadonlyBytes iv_in, ReadonlyBytes aad, ReadonlyBytes tag) { - auto iv_buf_result = ByteBuffer::copy(iv_in); - // Not enough memory to figure out :shrug: - if (iv_buf_result.is_error()) - return VerificationConsistency::Inconsistent; + auto iv_buf = process_iv(iv_in); + auto iv = iv_buf.bytes(); - auto iv = iv_buf_result.value().bytes(); - - // Increment the IV for block 0 - CTR::increment(iv); typename T::BlockType block0; block0.overwrite(iv); this->cipher().encrypt_block(block0, block0); @@ -119,7 +138,8 @@ class GCM : public CTR { block0.apply_initialization_vector({ auth_tag.data, array_size(auth_tag.data) }); auto test_consistency = [&] { - if (block0.block_size() != tag.size() || !timing_safe_compare(block0.bytes().data(), tag.data(), tag.size())) + VERIFY(block0.block_size() >= tag.size()); + if (block0.block_size() < tag.size() || !timing_safe_compare(block0.bytes().data(), tag.data(), tag.size())) return VerificationConsistency::Inconsistent; return VerificationConsistency::Consistent; diff --git a/Libraries/LibTLS/Record.cpp b/Libraries/LibTLS/Record.cpp index ad800b1d0bc52..907421b10dcd6 100644 --- a/Libraries/LibTLS/Record.cpp +++ b/Libraries/LibTLS/Record.cpp @@ -154,13 +154,10 @@ void TLSv12::update_packet(ByteBuffer& packet) // AEAD IV (12) // IV (4) // (Nonce) (8) - // -- Our GCM impl takes 16 bytes - // zero (4) - u8 iv[16]; - Bytes iv_bytes { iv, 16 }; + u8 iv[12]; + Bytes iv_bytes { iv, 12 }; Bytes { m_context.crypto.local_aead_iv, 4 }.copy_to(iv_bytes); fill_with_random(iv_bytes.slice(4, 8)); - memset(iv_bytes.offset(12), 0, 4); // write the random part of the iv out iv_bytes.slice(4, 8).copy_to(ct.bytes().slice(header_size)); @@ -400,13 +397,10 @@ ssize_t TLSv12::handle_message(ReadonlyBytes buffer) // AEAD IV (12) // IV (4) // (Nonce) (8) - // -- Our GCM impl takes 16 bytes - // zero (4) - u8 iv[16]; - Bytes iv_bytes { iv, 16 }; + u8 iv[12]; + Bytes iv_bytes { iv, 12 }; Bytes { m_context.crypto.remote_aead_iv, 4 }.copy_to(iv_bytes); nonce.copy_to(iv_bytes.slice(4)); - memset(iv_bytes.offset(12), 0, 4); auto ciphertext = payload.slice(0, payload.size() - 16); auto tag = payload.slice(ciphertext.size()); diff --git a/Tests/LibCrypto/TestAES.cpp b/Tests/LibCrypto/TestAES.cpp index f84101393e0e9..cb9f0d14e8aa5 100644 --- a/Tests/LibCrypto/TestAES.cpp +++ b/Tests/LibCrypto/TestAES.cpp @@ -466,7 +466,7 @@ TEST_CASE(test_AES_GCM_128bit_encrypt_empty) u8 result_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; Bytes out; auto tag = ByteBuffer::create_uninitialized(16).release_value(); - cipher.encrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, tag); + cipher.encrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, tag); EXPECT(memcmp(result_tag, tag.data(), tag.size()) == 0); } @@ -478,7 +478,7 @@ TEST_CASE(test_AES_GCM_128bit_encrypt_zeros) auto tag = ByteBuffer::create_uninitialized(16).release_value(); auto out = ByteBuffer::create_uninitialized(16).release_value(); auto out_bytes = out.bytes(); - cipher.encrypt("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, tag); + cipher.encrypt("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, tag); EXPECT(memcmp(result_ct, out.data(), out.size()) == 0); EXPECT(memcmp(result_tag, tag.data(), tag.size()) == 0); } @@ -494,7 +494,7 @@ TEST_CASE(test_AES_GCM_128bit_encrypt_multiple_blocks_with_iv) cipher.encrypt( "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b, out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88"_b, {}, tag); EXPECT(memcmp(result_ct, out.data(), out.size()) == 0); @@ -512,7 +512,7 @@ TEST_CASE(test_AES_GCM_128bit_encrypt_with_aad) cipher.encrypt( "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b, out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88"_b, {}, tag); EXPECT(memcmp(result_ct, out.data(), out.size()) == 0); @@ -524,7 +524,7 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_empty) Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); u8 input_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; Bytes out; - auto consistency = cipher.decrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); + auto consistency = cipher.decrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); EXPECT_EQ(consistency, Crypto::VerificationConsistency::Consistent); EXPECT_EQ(out.size(), 0u); } @@ -537,7 +537,7 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_zeros) u8 result_pt[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; auto out = ByteBuffer::create_uninitialized(16).release_value(); auto out_bytes = out.bytes(); - auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); + auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); EXPECT_EQ(consistency, Crypto::VerificationConsistency::Consistent); EXPECT(memcmp(result_pt, out.data(), out.size()) == 0); } @@ -550,7 +550,7 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_multiple_blocks_with_iv) u8 result_pt[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; auto out = ByteBuffer::create_uninitialized(16).release_value(); auto out_bytes = out.bytes(); - auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); + auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, {}, { input_tag, 16 }); EXPECT_EQ(consistency, Crypto::VerificationConsistency::Consistent); EXPECT(memcmp(result_pt, out.data(), out.size()) == 0); } @@ -566,7 +566,7 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_multiple_blocks_with_aad) auto consistency = cipher.decrypt( { input_ct, 64 }, out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88"_b, "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b, { input_tag, 16 }); EXPECT(memcmp(result_pt, out.data(), out.size()) == 0); From 6ef8b54d2149a5512a8786512a5eb98c29152afe Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 15 Dec 2024 21:45:50 +0100 Subject: [PATCH 207/237] LibWeb: Add support for AES-GCM encrypt and decrypt Adds ~400 WPT test passes. --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 49 ++- .../encrypt_decrypt/aes_gcm.https.any.txt | 330 ++++++++++++++++++ .../aes_gcm_256_iv.https.any.txt | 330 ++++++++++++++++++ .../wrapKey_unwrapKey.https.any.txt | 145 ++++---- .../WebCryptoAPI/encrypt_decrypt/aes.js | 325 +++++++++++++++++ .../encrypt_decrypt/aes_gcm.https.any.html | 18 + .../encrypt_decrypt/aes_gcm.https.any.js | 7 + .../aes_gcm_256_iv.https.any.html | 18 + .../aes_gcm_256_iv.https.any.js | 7 + .../aes_gcm_256_iv_fixtures.js | 210 +++++++++++ .../encrypt_decrypt/aes_gcm_96_iv_fixtures.js | 209 +++++++++++ .../encrypt_decrypt/aes_gcm_vectors.js | 76 ++++ 12 files changed, 1626 insertions(+), 98 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv_fixtures.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_96_iv_fixtures.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_vectors.js diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 86bd341380073..6e1962be80b06 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -2021,11 +2021,15 @@ WebIDL::ExceptionOr> AesGcm::encrypt(AlgorithmParams co { auto const& normalized_algorithm = static_cast(params); - // FIXME: 1. If plaintext has a length greater than 2^39 - 256 bytes, then throw an OperationError. + // 1. If plaintext has a length greater than 2^39 - 256 bytes, then throw an OperationError. + if (plaintext.size() > (1ULL << 39) - 256) + return WebIDL::OperationError::create(m_realm, "Invalid plaintext length"_string); - // FIXME: 2. If the iv member of normalizedAlgorithm has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // 2. If the iv member of normalizedAlgorithm has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // NOTE: This is not possible - // FIXME: 3. If the additionalData member of normalizedAlgorithm is present and has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // 3. If the additionalData member of normalizedAlgorithm is present and has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // NOTE: This is not possible // 4. If the tagLength member of normalizedAlgorithm is not present: Let tagLength be 128. auto tag_length = 0; @@ -2055,19 +2059,16 @@ WebIDL::ExceptionOr> AesGcm::encrypt(AlgorithmParams co auto key_bytes = key->handle().get(); ::Crypto::Cipher::AESCipher::GCMMode cipher(key_bytes, key_length, ::Crypto::Cipher::Intent::Encryption); - ByteBuffer ciphertext = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(plaintext.size())); - ByteBuffer tag = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(tag_length / 8)); - [[maybe_unused]] Bytes ciphertext_span = ciphertext.bytes(); - [[maybe_unused]] Bytes tag_span = tag.bytes(); + auto ciphertext = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(plaintext.size())); + auto tag = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(tag_length / 8)); - // FIXME: cipher.encrypt(plaintext, ciphertext_span, normalized_algorithm.iv, additional_data, tag_span); - return WebIDL::NotSupportedError::create(m_realm, "AES GCM encryption not yet implemented"_string); + cipher.encrypt(plaintext, ciphertext.bytes(), normalized_algorithm.iv, additional_data, tag.bytes()); // 7. Let ciphertext be equal to C | T, where '|' denotes concatenation. - // TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.try_append(tag)); + TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.try_append(tag)); // 8. Return the result of creating an ArrayBuffer containing ciphertext. - // return JS::ArrayBuffer::create(m_realm, ciphertext); + return JS::ArrayBuffer::create(m_realm, ciphertext); } WebIDL::ExceptionOr> AesGcm::decrypt(AlgorithmParams const& params, GC::Ref key, ByteBuffer const& ciphertext) @@ -2092,16 +2093,18 @@ WebIDL::ExceptionOr> AesGcm::decrypt(AlgorithmParams co if (ciphertext.size() < tag_length / 8) return WebIDL::OperationError::create(m_realm, "Invalid ciphertext length"_string); - // FIXME: 3. If the iv member of normalizedAlgorithm has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // 3. If the iv member of normalizedAlgorithm has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // NOTE: This is not possible - // FIXME: 4. If the additionalData member of normalizedAlgorithm is present and has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // 4. If the additionalData member of normalizedAlgorithm is present and has a length greater than 2^64 - 1 bytes, then throw an OperationError. + // NOTE: This is not possible // 5. Let tag be the last tagLength bits of ciphertext. - auto tag_bits = tag_length / 8; - auto tag = TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.slice(ciphertext.size() - tag_bits, tag_bits)); + auto tag_bytes = tag_length / 8; + auto tag = TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.slice(ciphertext.size() - tag_bytes, tag_bytes)); // 6. Let actualCiphertext be the result of removing the last tagLength bits from ciphertext. - auto actual_ciphertext = TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.slice(0, ciphertext.size() - tag_bits)); + auto actual_ciphertext = TRY_OR_THROW_OOM(m_realm->vm(), ciphertext.slice(0, ciphertext.size() - tag_bytes)); // 7. Let additionalData be the contents of the additionalData member of normalizedAlgorithm if present or the empty octet string otherwise. auto additional_data = normalized_algorithm.additional_data.value_or(ByteBuffer {}); @@ -2118,22 +2121,18 @@ WebIDL::ExceptionOr> AesGcm::decrypt(AlgorithmParams co auto key_bytes = key->handle().get(); ::Crypto::Cipher::AESCipher::GCMMode cipher(key_bytes, key_length, ::Crypto::Cipher::Intent::Decryption); - ByteBuffer plaintext = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(actual_ciphertext.size())); - [[maybe_unused]] Bytes plaintext_span = plaintext.bytes(); - [[maybe_unused]] Bytes actual_ciphertext_span = actual_ciphertext.bytes(); - [[maybe_unused]] Bytes tag_span = tag.bytes(); + auto plaintext = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_zeroed(actual_ciphertext.size())); - // FIXME: auto result = cipher.decrypt(ciphertext, plaintext_span, normalized_algorithm.iv, additional_data, tag_span); - return WebIDL::NotSupportedError::create(m_realm, "AES GCM decryption not yet implemented"_string); + auto result = cipher.decrypt(actual_ciphertext.bytes(), plaintext.bytes(), normalized_algorithm.iv, additional_data, tag.bytes()); // If the result of the algorithm is the indication of inauthenticity, "FAIL": throw an OperationError - // if (result == ::Crypto::VerificationConsistency::Inconsistent) - // return WebIDL::OperationError::create(m_realm, "Decryption failed"_string); + if (result == ::Crypto::VerificationConsistency::Inconsistent) + return WebIDL::OperationError::create(m_realm, "Decryption failed"_string); // Otherwise: Let plaintext be the output P of the Authenticated Decryption Function. // 9. Return the result of creating an ArrayBuffer containing plaintext. - // return JS::ArrayBuffer::create(m_realm, plaintext); + return JS::ArrayBuffer::create(m_realm, plaintext); } WebIDL::ExceptionOr, GC::Ref>> AesGcm::generate_key(AlgorithmParams const& params, bool extractable, Vector const& key_usages) diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.txt new file mode 100644 index 0000000000000..bfc5fa7a180d7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.txt @@ -0,0 +1,330 @@ +Harness status: OK + +Found 325 tests + +325 Pass +Pass setup +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv decryption +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 96-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 24-bits +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 48-bits +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 72-bits +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 95-bits +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 129-bits +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 24-bits +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 48-bits +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 72-bits +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 95-bits +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 129-bits +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 24-bits +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 48-bits +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 72-bits +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 95-bits +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 129-bits +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 128-bit key, 96-bit iv, illegal tag length 129-bits decryption +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 192-bit key, 96-bit iv, illegal tag length 129-bits decryption +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 256-bit key, 96-bit iv, illegal tag length 129-bits decryption \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.txt new file mode 100644 index 0000000000000..28dc5e00037ba --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.txt @@ -0,0 +1,330 @@ +Harness status: OK + +Found 325 tests + +325 Pass +Pass setup +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv with altered plaintext +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv decryption +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv decryption with altered ciphertext +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv without encrypt usage +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv with mismatched key and algorithm +Pass AES-GCM 128-bit key, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, no additional data, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 192-bit key, no additional data, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 32-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 64-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 96-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 104-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 112-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 120-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 256-bit key, no additional data, 128-bit tag, 256-bit iv without decrypt usage +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 24-bits +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 48-bits +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 72-bits +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 95-bits +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 129-bits +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 24-bits +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 48-bits +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 72-bits +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 95-bits +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 129-bits +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 24-bits +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 48-bits +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 72-bits +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 95-bits +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 129-bits +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 128-bit key, 256-bit iv, illegal tag length 129-bits decryption +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 192-bit key, 256-bit iv, illegal tag length 129-bits decryption +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 24-bits decryption +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 48-bits decryption +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 72-bits decryption +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 95-bits decryption +Pass AES-GCM 256-bit key, 256-bit iv, illegal tag length 129-bits decryption \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt index f08f7c144ee98..23d05812f6bde 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt @@ -2,8 +2,7 @@ Harness status: OK Found 244 tests -173 Pass -71 Fail +244 Pass Pass setup Pass Can wrap and unwrap RSA-OAEP public key keys using spki and RSA-OAEP Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and RSA-OAEP @@ -53,10 +52,10 @@ Pass Can wrap and unwrap AES-CBC keys using jwk and RSA-OAEP Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and RSA-OAEP Pass Can unwrap AES-CBC non-extractable keys using jwk and RSA-OAEP Pass Can wrap and unwrap AES-GCM keys using raw and RSA-OAEP -Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP Pass Can wrap and unwrap AES-GCM keys using jwk and RSA-OAEP -Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP -Fail Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP Pass Can wrap and unwrap HMAC keys using raw and RSA-OAEP Pass Can wrap and unwrap HMAC keys as non-extractable using raw and RSA-OAEP Pass Can wrap and unwrap HMAC keys using jwk and RSA-OAEP @@ -115,10 +114,10 @@ Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CTR Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CTR Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CTR Pass Can wrap and unwrap AES-GCM keys using raw and AES-CTR -Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CTR -Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR -Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR +Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR Pass Can wrap and unwrap HMAC keys using raw and AES-CTR Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CTR Pass Can wrap and unwrap HMAC keys using jwk and AES-CTR @@ -177,74 +176,74 @@ Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CBC Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CBC Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CBC Pass Can wrap and unwrap AES-GCM keys using raw and AES-CBC -Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CBC -Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC -Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC +Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC Pass Can wrap and unwrap HMAC keys using raw and AES-CBC Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CBC Pass Can wrap and unwrap HMAC keys using jwk and AES-CBC Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-CBC Pass Can unwrap HMAC non-extractable keys using jwk and AES-CBC -Fail Can wrap and unwrap RSA-OAEP public key keys using spki and AES-GCM -Fail Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-GCM -Fail Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-GCM -Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDSA public key keys using spki and AES-GCM -Fail Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDH public key keys using spki and AES-GCM -Fail Can wrap and unwrap ECDH public key keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDH private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap ECDH private key keys using jwk and AES-GCM -Fail Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap ECDH private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap Ed25519 public key keys using spki and AES-GCM -Fail Can wrap and unwrap Ed25519 public key keys using jwk and AES-GCM -Fail Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap Ed25519 private key keys using jwk and AES-GCM -Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap Ed25519 private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap X25519 public key keys using spki and AES-GCM -Fail Can wrap and unwrap X25519 public key keys using jwk and AES-GCM -Fail Can wrap and unwrap X25519 private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap X25519 private key keys using jwk and AES-GCM -Fail Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap X25519 private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap X448 public key keys using spki and AES-GCM -Fail Can wrap and unwrap X448 public key keys using jwk and AES-GCM -Fail Can wrap and unwrap X448 private key keys using pkcs8 and AES-GCM -Fail Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-GCM -Fail Can wrap and unwrap X448 private key keys using jwk and AES-GCM -Fail Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-GCM -Fail Can unwrap X448 private key non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-CTR keys using raw and AES-GCM -Fail Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-GCM -Fail Can wrap and unwrap AES-CTR keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-GCM -Fail Can unwrap AES-CTR non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-CBC keys using raw and AES-GCM -Fail Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-GCM -Fail Can wrap and unwrap AES-CBC keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-GCM -Fail Can unwrap AES-CBC non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-GCM keys using raw and AES-GCM -Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-GCM -Fail Can wrap and unwrap AES-GCM keys using jwk and AES-GCM -Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-GCM -Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-GCM -Fail Can wrap and unwrap HMAC keys using raw and AES-GCM -Fail Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM -Fail Can wrap and unwrap HMAC keys using jwk and AES-GCM -Fail Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM -Fail Can unwrap HMAC non-extractable keys using jwk and AES-GCM \ No newline at end of file +Pass Can wrap and unwrap RSA-OAEP public key keys using spki and AES-GCM +Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-GCM +Pass Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-GCM +Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDSA public key keys using spki and AES-GCM +Pass Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDH public key keys using spki and AES-GCM +Pass Can wrap and unwrap ECDH public key keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDH private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap ECDH private key keys using jwk and AES-GCM +Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap ECDH private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap Ed25519 public key keys using spki and AES-GCM +Pass Can wrap and unwrap Ed25519 public key keys using jwk and AES-GCM +Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap Ed25519 private key keys using jwk and AES-GCM +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap Ed25519 private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap X25519 public key keys using spki and AES-GCM +Pass Can wrap and unwrap X25519 public key keys using jwk and AES-GCM +Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap X25519 private key keys using jwk and AES-GCM +Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap X25519 private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap X448 public key keys using spki and AES-GCM +Pass Can wrap and unwrap X448 public key keys using jwk and AES-GCM +Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-GCM +Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-GCM +Pass Can wrap and unwrap X448 private key keys using jwk and AES-GCM +Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-GCM +Pass Can unwrap X448 private key non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-CTR keys using raw and AES-GCM +Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-GCM +Pass Can wrap and unwrap AES-CTR keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-GCM +Pass Can unwrap AES-CTR non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-CBC keys using raw and AES-GCM +Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-GCM +Pass Can wrap and unwrap AES-CBC keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-GCM +Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-GCM keys using raw and AES-GCM +Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-GCM +Pass Can wrap and unwrap AES-GCM keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-GCM +Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap HMAC keys using raw and AES-GCM +Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM +Pass Can wrap and unwrap HMAC keys using jwk and AES-GCM +Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM +Pass Can unwrap HMAC non-extractable keys using jwk and AES-GCM \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes.js new file mode 100644 index 0000000000000..fdeb7963f761e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes.js @@ -0,0 +1,325 @@ + +function run_test() { + var subtle = self.crypto.subtle; // Change to test prefixed implementations + + // When are all these tests really done? When all the promises they use have resolved. + var all_promises = []; + + // Source file aes_XXX_vectors.js provides the getTestVectors method + // for the AES-XXX algorithm that drives these tests. + var vectors = getTestVectors(); + var passingVectors = vectors.passing; + var failingVectors = vectors.failing; + var decryptionFailingVectors = vectors.decryptionFailing; + + // Check for successful encryption. + passingVectors.forEach(function(vector) { + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext) + .then(function(result) { + assert_true(equalBuffers(result, vector.result), "Should return expected result"); + }, function(err) { + assert_unreached("encrypt error for test " + vector.name + ": " + err.message); + }); + }, vector.name); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name); + }); + + all_promises.push(promise); + }); + + // Check for successful encryption even if the buffer is changed after calling encrypt. + passingVectors.forEach(function(vector) { + var plaintext = copyBuffer(vector.plaintext); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.encrypt(vector.algorithm, vector.key, plaintext) + .then(function(result) { + assert_true(equalBuffers(result, vector.result), "Should return expected result"); + }, function(err) { + assert_unreached("encrypt error for test " + vector.name + ": " + err.message); + }); + plaintext[0] = 255 - plaintext[0]; + return operation; + }, vector.name + " with altered plaintext"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " with altered plaintext"); + }); + + all_promises.push(promise); + }); + + // Check for successful decryption. + passingVectors.forEach(function(vector) { + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.decrypt(vector.algorithm, vector.key, vector.result) + .then(function(result) { + assert_true(equalBuffers(result, vector.plaintext), "Should return expected result"); + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": " + err.message); + }); + }, vector.name + " decryption"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step for decryption: " + vector.name); + }); + + all_promises.push(promise); + }); + + // Check for successful decryption even if ciphertext is altered. + passingVectors.forEach(function(vector) { + var ciphertext = copyBuffer(vector.result); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.decrypt(vector.algorithm, vector.key, ciphertext) + .then(function(result) { + assert_true(equalBuffers(result, vector.plaintext), "Should return expected result"); + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": " + err.message); + }); + ciphertext[0] = 255 - ciphertext[0]; + return operation; + }, vector.name + " decryption with altered ciphertext"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step for decryption: " + vector.name + " with altered ciphertext"); + }); + + all_promises.push(promise); + }); + + // Everything that succeeded should fail if no "encrypt" usage. + passingVectors.forEach(function(vector) { + // Don't want to overwrite key being used for success tests! + var badVector = Object.assign({}, vector); + badVector.key = null; + + var promise = importVectorKey(badVector, ["decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext) + .then(function(result) { + assert_unreached("should have thrown exception for test " + vector.name); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw an InvalidAccessError instead of " + err.message) + }); + }, vector.name + " without encrypt usage"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " without encrypt usage"); + }); + + all_promises.push(promise); + }); + + // Encryption should fail if algorithm of key doesn't match algorithm of function call. + passingVectors.forEach(function(vector) { + var algorithm = Object.assign({}, vector.algorithm); + if (algorithm.name === "AES-CBC") { + algorithm.name = "AES-CTR"; + algorithm.counter = new Uint8Array(16); + algorithm.length = 64; + } else { + algorithm.name = "AES-CBC"; + algorithm.iv = new Uint8Array(16); // Need syntactically valid parameter to get to error being checked. + } + + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.encrypt(algorithm, vector.key, vector.plaintext) + .then(function(result) { + assert_unreached("encrypt succeeded despite mismatch " + vector.name + ": " + err.message); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Mismatch should cause InvalidAccessError instead of " + err.message); + }); + }, vector.name + " with mismatched key and algorithm"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " with mismatched key and algorithm"); + }); + + all_promises.push(promise); + }); + + // Everything that succeeded decrypting should fail if no "decrypt" usage. + passingVectors.forEach(function(vector) { + // Don't want to overwrite key being used for success tests! + var badVector = Object.assign({}, vector); + badVector.key = null; + + var promise = importVectorKey(badVector, ["encrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.decrypt(vector.algorithm, vector.key, vector.result) + .then(function(result) { + assert_unreached("should have thrown exception for test " + vector.name); + }, function(err) { + assert_equals(err.name, "InvalidAccessError", "Should throw an InvalidAccessError instead of " + err.message) + }); + }, vector.name + " without decrypt usage"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " without decrypt usage"); + }); + + all_promises.push(promise); + }); + + // Check for OperationError due to data lengths. + failingVectors.forEach(function(vector) { + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext) + .then(function(result) { + assert_unreached("should have thrown exception for test " + vector.name); + }, function(err) { + assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message) + }); + }, vector.name); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name); + }); + + all_promises.push(promise); + }); + + // Check for OperationError due to data lengths for decryption, too. + failingVectors.forEach(function(vector) { + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.decrypt(vector.algorithm, vector.key, vector.result) + .then(function(result) { + assert_unreached("should have thrown exception for test " + vector.name); + }, function(err) { + assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message) + }); + }, vector.name + " decryption"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: decryption " + vector.name); + }); + + all_promises.push(promise); + }); + + // Check for decryption failing for algorithm-specific reasons (such as bad + // padding for AES-CBC). + decryptionFailingVectors.forEach(function(vector) { + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + return subtle.decrypt(vector.algorithm, vector.key, vector.result) + .then(function(result) { + assert_unreached("should have thrown exception for test " + vector.name); + }, function(err) { + assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message) + }); + }, vector.name); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: decryption " + vector.name); + }); + + all_promises.push(promise); + }); + + promise_test(function() { + return Promise.all(all_promises) + .then(function() {done();}) + .catch(function() {done();}) + }, "setup"); + + // A test vector has all needed fields for encryption, EXCEPT that the + // key field may be null. This function replaces that null with the Correct + // CryptoKey object. + // + // Returns a Promise that yields an updated vector on success. + function importVectorKey(vector, usages) { + if (vector.key !== null) { + return new Promise(function(resolve, reject) { + resolve(vector); + }); + } else { + return subtle.importKey("raw", vector.keyBuffer, {name: vector.algorithm.name}, false, usages) + .then(function(key) { + vector.key = key; + return vector; + }); + } + } + + // Returns a copy of the sourceBuffer it is sent. + function copyBuffer(sourceBuffer) { + var source = new Uint8Array(sourceBuffer); + var copy = new Uint8Array(sourceBuffer.byteLength) + + for (var i=0; i + +WebCryptoAPI: encrypt() Using AES-GCM w/ 96-bit iv + + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.js new file mode 100644 index 0000000000000..6e3a6efb12d7b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm.https.any.js @@ -0,0 +1,7 @@ +// META: title=WebCryptoAPI: encrypt() Using AES-GCM w/ 96-bit iv +// META: script=aes_gcm_96_iv_fixtures.js +// META: script=aes_gcm_vectors.js +// META: script=aes.js +// META: timeout=long + +run_test(); diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.html new file mode 100644 index 0000000000000..bd36cb05c04ad --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.html @@ -0,0 +1,18 @@ + + +WebCryptoAPI: encrypt() Using AES-GCM w/ 256-bit iv + + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.js new file mode 100644 index 0000000000000..92900fb13ae2e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv.https.any.js @@ -0,0 +1,7 @@ +// META: title=WebCryptoAPI: encrypt() Using AES-GCM w/ 256-bit iv +// META: script=aes_gcm_256_iv_fixtures.js +// META: script=aes_gcm_vectors.js +// META: script=aes.js +// META: timeout=long + +run_test(); diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv_fixtures.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv_fixtures.js new file mode 100644 index 0000000000000..9cdbbbb79075c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_256_iv_fixtures.js @@ -0,0 +1,210 @@ +function getFixtures() { + // Before we can really start, we need to fill a bunch of buffers with data + var plaintext = new Uint8Array([ + 84, 104, 105, 115, 32, 115, 112, 101, 99, 105, 102, 105, 99, 97, 116, 105, + 111, 110, 32, 100, 101, 115, 99, 114, 105, 98, 101, 115, 32, 97, 32, 74, 97, + 118, 97, 83, 99, 114, 105, 112, 116, 32, 65, 80, 73, 32, 102, 111, 114, 32, + 112, 101, 114, 102, 111, 114, 109, 105, 110, 103, 32, 98, 97, 115, 105, 99, + 32, 99, 114, 121, 112, 116, 111, 103, 114, 97, 112, 104, 105, 99, 32, 111, + 112, 101, 114, 97, 116, 105, 111, 110, 115, 32, 105, 110, 32, 119, 101, 98, + 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 115, + 117, 99, 104, 32, 97, 115, 32, 104, 97, 115, 104, 105, 110, 103, 44, 32, + 115, 105, 103, 110, 97, 116, 117, 114, 101, 32, 103, 101, 110, 101, 114, 97, + 116, 105, 111, 110, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 105, 99, + 97, 116, 105, 111, 110, 44, 32, 97, 110, 100, 32, 101, 110, 99, 114, 121, + 112, 116, 105, 111, 110, 32, 97, 110, 100, 32, 100, 101, 99, 114, 121, 112, + 116, 105, 111, 110, 46, 32, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, + 108, 121, 44, 32, 105, 116, 32, 100, 101, 115, 99, 114, 105, 98, 101, 115, + 32, 97, 110, 32, 65, 80, 73, 32, 102, 111, 114, 32, 97, 112, 112, 108, 105, + 99, 97, 116, 105, 111, 110, 115, 32, 116, 111, 32, 103, 101, 110, 101, 114, + 97, 116, 101, 32, 97, 110, 100, 47, 111, 114, 32, 109, 97, 110, 97, 103, + 101, 32, 116, 104, 101, 32, 107, 101, 121, 105, 110, 103, 32, 109, 97, 116, + 101, 114, 105, 97, 108, 32, 110, 101, 99, 101, 115, 115, 97, 114, 121, 32, + 116, 111, 32, 112, 101, 114, 102, 111, 114, 109, 32, 116, 104, 101, 115, + 101, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 115, 46, 32, 85, 115, + 101, 115, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 65, 80, 73, 32, + 114, 97, 110, 103, 101, 32, 102, 114, 111, 109, 32, 117, 115, 101, 114, 32, + 111, 114, 32, 115, 101, 114, 118, 105, 99, 101, 32, 97, 117, 116, 104, 101, + 110, 116, 105, 99, 97, 116, 105, 111, 110, 44, 32, 100, 111, 99, 117, 109, + 101, 110, 116, 32, 111, 114, 32, 99, 111, 100, 101, 32, 115, 105, 103, 110, + 105, 110, 103, 44, 32, 97, 110, 100, 32, 116, 104, 101, 32, 99, 111, 110, + 102, 105, 100, 101, 110, 116, 105, 97, 108, 105, 116, 121, 32, 97, 110, 100, + 32, 105, 110, 116, 101, 103, 114, 105, 116, 121, 32, 111, 102, 32, 99, 111, + 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 115, 46, + ]); + + // We want some random key bytes of various sizes. + // These were randomly generated from a script. + var keyBytes = { + 128: new Uint8Array([ + 222, 192, 212, 252, 191, 60, 71, 65, 200, 146, 218, 189, 28, 212, 192, 78, + ]), + 192: new Uint8Array([ + 208, 238, 131, 65, 63, 68, 196, 63, 186, 208, 61, 207, 166, 18, 99, 152, + 29, 109, 221, 95, 240, 30, 28, 246, + ]), + 256: new Uint8Array([ + 103, 105, 56, 35, 251, 29, 88, 7, 63, 145, 236, 233, 204, 58, 249, 16, + 229, 83, 38, 22, 164, 210, 123, 19, 235, 123, 116, 216, 0, 11, 191, 48, + ]), + }; + + // AES-GCM needs an IV of no more than 2^64 - 1 bytes. Arbitrary 32 bytes is okay then. + var iv = new Uint8Array([ + 58, 146, 115, 42, 166, 234, 57, 191, 57, 134, 224, 199, 63, 169, 32, 0, 32, + 33, 117, 56, 94, 248, 173, 234, 194, 200, 115, 53, 235, 146, 141, 212, + ]); + + // Authenticated encryption via AES-GCM requires additional data that + // will be checked. We use the ASCII encoded Editorial Note + // following the Abstract of the Web Cryptography API recommendation. + var additionalData = new Uint8Array([ + 84, 104, 101, 114, 101, 32, 97, 114, 101, 32, 55, 32, 102, 117, 114, 116, + 104, 101, 114, 32, 101, 100, 105, 116, 111, 114, 105, 97, 108, 32, 110, 111, + 116, 101, 115, 32, 105, 110, 32, 116, 104, 101, 32, 100, 111, 99, 117, 109, + 101, 110, 116, 46, + ]); + + // The length of the tag defaults to 16 bytes (128 bit). + var tag = { + 128: new Uint8Array([ + 194, 226, 198, 253, 239, 28, 197, 240, 123, 216, 176, 151, 239, 200, 184, + 183, + ]), + 192: new Uint8Array([ + 183, 57, 32, 144, 164, 76, 121, 77, 58, 86, 62, 132, 53, 130, 96, 225, + ]), + 256: new Uint8Array([ + 188, 239, 241, 48, 159, 21, 213, 0, 241, 42, 85, 76, 194, 28, 49, 60, + ]), + }; + + var tag_with_empty_ad = { + 128: new Uint8Array([ + 222, 51, 11, 23, 36, 222, 250, 248, 27, 98, 30, 81, 150, 35, 220, 198, + ]), + 192: new Uint8Array([ + 243, 11, 130, 112, 169, 239, 114, 238, 185, 219, 93, 1, 95, 108, 184, 183, + ]), + 256: new Uint8Array([ + 244, 186, 86, 203, 154, 37, 191, 248, 246, 57, 139, 130, 224, 47, 217, + 238, + ]), + }; + + // Results. These were created using the Python cryptography module. + + // AES-GCM produces ciphertext and a tag. + var ciphertext = { + 128: new Uint8Array([ + 180, 241, 40, 183, 105, 52, 147, 238, 224, 175, 175, 236, 168, 244, 241, + 121, 9, 202, 225, 237, 56, 216, 253, 254, 186, 102, 111, 207, 228, 190, + 130, 177, 159, 246, 6, 53, 249, 113, 228, 254, 81, 126, 253, 191, 100, 43, + 251, 147, 107, 91, 166, 231, 201, 241, 180, 214, 112, 47, 123, 164, 186, + 134, 54, 65, 22, 181, 201, 82, 236, 59, 52, 139, 172, 39, 41, 89, 123, 62, + 102, 167, 82, 150, 250, 93, 96, 169, 135, 89, 245, 255, 164, 192, 169, + 159, 25, 16, 139, 145, 76, 4, 144, 131, 148, 197, 204, 46, 23, 110, 193, + 228, 127, 120, 242, 24, 54, 240, 181, 162, 98, 244, 249, 68, 134, 122, + 126, 151, 38, 108, 116, 68, 150, 109, 38, 194, 21, 159, 140, 205, 183, 35, + 97, 151, 186, 120, 145, 22, 235, 22, 210, 223, 187, 143, 162, 183, 93, + 196, 104, 51, 96, 53, 234, 250, 184, 76, 237, 157, 37, 203, 226, 87, 222, + 75, 240, 95, 218, 222, 64, 81, 165, 75, 201, 216, 190, 13, 116, 217, 69, + 66, 47, 161, 68, 247, 74, 253, 157, 181, 162, 121, 53, 32, 91, 124, 230, + 105, 224, 17, 187, 50, 61, 77, 103, 79, 71, 57, 163, 116, 234, 149, 27, + 105, 24, 31, 159, 3, 128, 130, 42, 94, 125, 200, 142, 251, 148, 201, 17, + 149, 232, 84, 50, 17, 18, 203, 186, 226, 164, 227, 202, 76, 65, 16, 163, + 224, 132, 52, 31, 101, 129, 72, 171, 159, 42, 177, 253, 98, 86, 201, 95, + 117, 62, 12, 205, 78, 36, 126, 196, 121, 89, 185, 37, 161, 66, 181, 117, + 186, 71, 124, 132, 110, 120, 27, 246, 163, 18, 13, 90, 200, 127, 82, 209, + 241, 170, 73, 247, 137, 96, 244, 254, 251, 119, 71, 156, 27, 107, 53, 33, + 45, 22, 0, 144, 48, 32, 11, 116, 21, 125, 246, 217, 171, 158, 224, 142, + 234, 141, 242, 168, 89, 154, 66, 227, 161, 182, 96, 1, 88, 78, 12, 7, 239, + 30, 206, 31, 89, 111, 107, 42, 37, 241, 148, 232, 1, 8, 251, 117, 146, + 183, 9, 48, 39, 94, 59, 70, 230, 26, 165, 97, 156, 140, 141, 31, 62, 10, + 206, 55, 48, 207, 0, 197, 202, 197, 108, 133, 175, 80, 4, 16, 154, 223, + 255, 4, 196, 188, 178, 240, 29, 13, 120, 5, 225, 202, 3, 35, 225, 158, 92, + 152, 73, 205, 107, 157, 224, 245, 99, 194, 171, 156, 245, 247, 183, 165, + 40, 62, 200, 110, 29, 151, 206, 100, 175, 88, 36, 242, 90, 4, 82, 73, 250, + 140, 245, 217, 9, 153, 35, 242, 206, 78, 197, 121, 115, 15, 80, 128, 101, + 191, 240, 91, 151, 249, 62, 62, 244, 18, 3, 17, 135, 222, 210, 93, 149, + 123, + ]), + + 192: new Uint8Array([ + 126, 160, 166, 112, 227, 212, 106, 186, 175, 70, 24, 28, 86, 149, 31, 154, + 156, 190, 244, 132, 44, 61, 149, 242, 105, 67, 17, 136, 7, 146, 153, 170, + 200, 214, 142, 205, 170, 225, 85, 44, 241, 159, 255, 234, 10, 13, 37, 48, + 255, 21, 141, 176, 60, 117, 73, 130, 247, 204, 144, 102, 167, 89, 203, + 235, 229, 129, 122, 253, 124, 179, 115, 118, 163, 157, 67, 141, 122, 146, + 209, 11, 112, 5, 230, 117, 123, 184, 243, 99, 83, 10, 31, 166, 96, 1, 121, + 44, 10, 241, 24, 43, 184, 187, 25, 239, 246, 176, 108, 230, 127, 25, 42, + 67, 202, 140, 179, 104, 159, 75, 103, 43, 248, 98, 166, 179, 67, 0, 163, + 227, 84, 40, 129, 227, 198, 205, 7, 156, 16, 185, 24, 166, 59, 218, 197, + 114, 74, 34, 126, 22, 226, 226, 85, 212, 69, 83, 163, 185, 68, 109, 182, + 54, 209, 237, 96, 184, 32, 53, 127, 175, 13, 146, 141, 115, 164, 184, 98, + 245, 174, 223, 46, 32, 167, 39, 103, 19, 210, 80, 131, 254, 103, 249, 247, + 29, 120, 31, 105, 241, 103, 169, 249, 93, 153, 74, 56, 53, 239, 157, 132, + 236, 169, 246, 242, 24, 113, 97, 128, 238, 152, 148, 31, 84, 8, 52, 105, + 198, 116, 103, 132, 48, 199, 23, 90, 24, 29, 63, 41, 117, 191, 57, 31, + 209, 128, 60, 119, 175, 84, 141, 177, 165, 169, 195, 35, 163, 105, 146, + 157, 209, 93, 149, 105, 160, 93, 231, 78, 201, 92, 235, 200, 89, 37, 50, + 181, 30, 213, 242, 59, 156, 219, 19, 158, 17, 224, 81, 108, 52, 87, 248, + 101, 23, 39, 107, 67, 151, 103, 230, 126, 202, 184, 118, 226, 18, 29, 93, + 37, 208, 40, 82, 113, 35, 157, 145, 152, 50, 253, 140, 47, 141, 192, 1, + 148, 114, 40, 10, 112, 79, 227, 16, 105, 247, 31, 49, 102, 195, 75, 183, + 172, 254, 188, 42, 89, 77, 38, 104, 1, 180, 106, 61, 71, 70, 35, 160, 103, + 101, 244, 26, 226, 37, 159, 155, 4, 107, 222, 219, 136, 37, 24, 246, 44, + 23, 44, 248, 132, 108, 59, 179, 99, 145, 132, 82, 53, 203, 111, 150, 55, + 123, 51, 214, 165, 108, 124, 179, 131, 174, 139, 224, 114, 96, 218, 181, + 243, 128, 198, 98, 115, 92, 95, 165, 23, 229, 108, 146, 14, 244, 162, 37, + 85, 201, 33, 44, 92, 106, 112, 185, 16, 189, 42, 114, 109, 59, 124, 131, + 16, 211, 31, 97, 29, 135, 61, 150, 75, 250, 207, 129, 38, 205, 187, 186, + 55, 207, 232, 24, 48, 232, 49, 226, 16, 12, 27, 70, 31, 124, 128, 218, + 100, 91, 200, 184, 78, 252, 100, 235, 62, 43, 69, 214, 163, 65, 14, 44, + 180, + ]), + + 256: new Uint8Array([ + 8, 97, 235, 113, 70, 32, 135, 131, 210, 209, 124, 160, 255, 182, 9, 29, + 125, 193, 27, 240, 129, 46, 2, 137, 169, 142, 61, 7, 145, 54, 170, 207, + 159, 111, 39, 95, 87, 63, 162, 27, 6, 18, 219, 215, 116, 34, 90, 57, 114, + 244, 102, 145, 67, 6, 51, 152, 247, 165, 242, 116, 100, 219, 177, 72, 177, + 17, 110, 67, 93, 219, 100, 217, 20, 207, 89, 154, 45, 37, 105, 83, 67, + 162, 140, 235, 129, 40, 177, 202, 174, 54, 148, 55, 156, 193, 232, 249, + 134, 163, 195, 51, 114, 116, 65, 38, 73, 99, 96, 249, 224, 69, 17, 119, + 186, 188, 181, 43, 78, 156, 76, 138, 226, 63, 5, 248, 9, 94, 26, 1, 2, + 235, 39, 174, 74, 47, 183, 22, 40, 47, 47, 13, 100, 119, 12, 67, 178, 184, + 56, 167, 238, 143, 13, 44, 208, 185, 151, 108, 6, 17, 52, 122, 182, 210, + 207, 42, 219, 37, 74, 94, 126, 36, 249, 37, 32, 4, 218, 44, 238, 69, 56, + 219, 31, 77, 173, 46, 187, 103, 36, 112, 213, 252, 40, 87, 164, 240, 163, + 159, 32, 129, 125, 178, 108, 47, 28, 31, 36, 42, 115, 36, 14, 145, 195, + 156, 191, 46, 163, 249, 181, 31, 90, 73, 30, 72, 57, 223, 63, 60, 79, 140, + 14, 117, 31, 145, 222, 156, 121, 237, 32, 145, 143, 96, 12, 254, 35, 21, + 21, 59, 168, 171, 154, 217, 0, 59, 202, 175, 103, 214, 192, 175, 26, 18, + 43, 54, 176, 222, 75, 22, 7, 122, 253, 224, 145, 61, 42, 208, 73, 237, 84, + 141, 209, 213, 228, 46, 244, 59, 9, 68, 6, 35, 88, 189, 10, 62, 9, 85, 28, + 44, 82, 19, 153, 160, 178, 240, 56, 160, 244, 201, 173, 77, 61, 20, 227, + 30, 180, 167, 16, 105, 185, 193, 95, 207, 41, 23, 134, 78, 198, 182, 93, + 24, 89, 247, 231, 75, 233, 194, 137, 242, 114, 194, 190, 130, 138, 238, + 94, 137, 193, 194, 115, 137, 190, 207, 169, 83, 155, 14, 210, 160, 129, + 195, 161, 234, 221, 255, 114, 67, 98, 12, 93, 41, 65, 183, 244, 103, 247, + 101, 82, 246, 125, 87, 125, 78, 21, 186, 102, 205, 20, 40, 32, 201, 174, + 15, 52, 240, 217, 180, 162, 108, 6, 211, 41, 18, 135, 232, 184, 18, 188, + 169, 157, 190, 76, 166, 75, 176, 127, 39, 251, 22, 203, 153, 80, 49, 241, + 124, 137, 151, 123, 204, 43, 159, 190, 177, 196, 18, 117, 169, 46, 152, + 251, 45, 25, 164, 27, 145, 214, 228, 55, 15, 2, 131, 216, 80, 255, 204, + 175, 100, 59, 145, 15, 103, 40, 33, 45, 255, 200, 254, 172, 138, 20, 58, + 87, 182, 192, 148, 219, 41, 88, 230, 229, 70, 249, + ]), + }; + + return { + plaintext, + keyBytes, + iv, + additionalData, + tag, + tag_with_empty_ad, + ciphertext, + }; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_96_iv_fixtures.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_96_iv_fixtures.js new file mode 100644 index 0000000000000..bb00e2d7dd92c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_96_iv_fixtures.js @@ -0,0 +1,209 @@ +function getFixtures() { + // Before we can really start, we need to fill a bunch of buffers with data + var plaintext = new Uint8Array([ + 84, 104, 105, 115, 32, 115, 112, 101, 99, 105, 102, 105, 99, 97, 116, 105, + 111, 110, 32, 100, 101, 115, 99, 114, 105, 98, 101, 115, 32, 97, 32, 74, 97, + 118, 97, 83, 99, 114, 105, 112, 116, 32, 65, 80, 73, 32, 102, 111, 114, 32, + 112, 101, 114, 102, 111, 114, 109, 105, 110, 103, 32, 98, 97, 115, 105, 99, + 32, 99, 114, 121, 112, 116, 111, 103, 114, 97, 112, 104, 105, 99, 32, 111, + 112, 101, 114, 97, 116, 105, 111, 110, 115, 32, 105, 110, 32, 119, 101, 98, + 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 115, + 117, 99, 104, 32, 97, 115, 32, 104, 97, 115, 104, 105, 110, 103, 44, 32, + 115, 105, 103, 110, 97, 116, 117, 114, 101, 32, 103, 101, 110, 101, 114, 97, + 116, 105, 111, 110, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 105, 99, + 97, 116, 105, 111, 110, 44, 32, 97, 110, 100, 32, 101, 110, 99, 114, 121, + 112, 116, 105, 111, 110, 32, 97, 110, 100, 32, 100, 101, 99, 114, 121, 112, + 116, 105, 111, 110, 46, 32, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, + 108, 121, 44, 32, 105, 116, 32, 100, 101, 115, 99, 114, 105, 98, 101, 115, + 32, 97, 110, 32, 65, 80, 73, 32, 102, 111, 114, 32, 97, 112, 112, 108, 105, + 99, 97, 116, 105, 111, 110, 115, 32, 116, 111, 32, 103, 101, 110, 101, 114, + 97, 116, 101, 32, 97, 110, 100, 47, 111, 114, 32, 109, 97, 110, 97, 103, + 101, 32, 116, 104, 101, 32, 107, 101, 121, 105, 110, 103, 32, 109, 97, 116, + 101, 114, 105, 97, 108, 32, 110, 101, 99, 101, 115, 115, 97, 114, 121, 32, + 116, 111, 32, 112, 101, 114, 102, 111, 114, 109, 32, 116, 104, 101, 115, + 101, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 115, 46, 32, 85, 115, + 101, 115, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 65, 80, 73, 32, + 114, 97, 110, 103, 101, 32, 102, 114, 111, 109, 32, 117, 115, 101, 114, 32, + 111, 114, 32, 115, 101, 114, 118, 105, 99, 101, 32, 97, 117, 116, 104, 101, + 110, 116, 105, 99, 97, 116, 105, 111, 110, 44, 32, 100, 111, 99, 117, 109, + 101, 110, 116, 32, 111, 114, 32, 99, 111, 100, 101, 32, 115, 105, 103, 110, + 105, 110, 103, 44, 32, 97, 110, 100, 32, 116, 104, 101, 32, 99, 111, 110, + 102, 105, 100, 101, 110, 116, 105, 97, 108, 105, 116, 121, 32, 97, 110, 100, + 32, 105, 110, 116, 101, 103, 114, 105, 116, 121, 32, 111, 102, 32, 99, 111, + 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 115, 46, + ]); + + // We want some random key bytes of various sizes. + // These were randomly generated from a script. + var keyBytes = { + 128: new Uint8Array([ + 222, 192, 212, 252, 191, 60, 71, 65, 200, 146, 218, 189, 28, 212, 192, 78, + ]), + 192: new Uint8Array([ + 208, 238, 131, 65, 63, 68, 196, 63, 186, 208, 61, 207, 166, 18, 99, 152, + 29, 109, 221, 95, 240, 30, 28, 246, + ]), + 256: new Uint8Array([ + 103, 105, 56, 35, 251, 29, 88, 7, 63, 145, 236, 233, 204, 58, 249, 16, + 229, 83, 38, 22, 164, 210, 123, 19, 235, 123, 116, 216, 0, 11, 191, 48, + ]), + }; + + // AES-GCM specification recommends that the IV should be 96 bits long. + var iv = new Uint8Array([ + 58, 146, 115, 42, 166, 234, 57, 191, 57, 134, 224, 199, + ]); + + // Authenticated encryption via AES-GCM requires additional data that + // will be checked. We use the ASCII encoded Editorial Note + // following the Abstract of the Web Cryptography API recommendation. + var additionalData = new Uint8Array([ + 84, 104, 101, 114, 101, 32, 97, 114, 101, 32, 55, 32, 102, 117, 114, 116, + 104, 101, 114, 32, 101, 100, 105, 116, 111, 114, 105, 97, 108, 32, 110, 111, + 116, 101, 115, 32, 105, 110, 32, 116, 104, 101, 32, 100, 111, 99, 117, 109, + 101, 110, 116, 46, + ]); + + // The length of the tag defaults to 16 bytes (128 bit). + var tag = { + 128: new Uint8Array([ + 180, 165, 14, 180, 121, 113, 220, 168, 254, 117, 18, 66, 110, 98, 146, + 240, + ]), + 192: new Uint8Array([ + 43, 102, 63, 121, 1, 120, 252, 2, 95, 149, 99, 207, 161, 10, 139, 159, + ]), + 256: new Uint8Array([ + 53, 0, 70, 11, 217, 64, 250, 241, 175, 160, 37, 78, 92, 160, 107, 38, + ]), + }; + + var tag_with_empty_ad = { + 128: new Uint8Array([ + 168, 116, 195, 94, 178, 179, 227, 160, 158, 207, 188, 132, 23, 137, 246, + 129, + ]), + 192: new Uint8Array([ + 111, 84, 157, 153, 12, 219, 247, 161, 220, 24, 0, 74, 203, 228, 83, 201, + ]), + 256: new Uint8Array([ + 125, 85, 225, 240, 220, 112, 144, 9, 168, 179, 251, 128, 126, 147, 131, + 244, + ]), + }; + + // Results. These were created using OpenSSL. + + // AES-GCM produces ciphertext and a tag. + var ciphertext = { + 128: new Uint8Array([ + 46, 244, 139, 198, 120, 180, 9, 39, 83, 58, 203, 107, 69, 71, 8, 165, 132, + 200, 94, 31, 228, 120, 170, 81, 241, 29, 38, 175, 99, 215, 241, 157, 144, + 97, 35, 42, 36, 231, 2, 94, 214, 140, 67, 48, 189, 242, 21, 208, 110, 179, + 30, 90, 181, 105, 242, 17, 244, 42, 42, 36, 125, 228, 82, 250, 87, 199, + 95, 168, 210, 57, 174, 20, 220, 188, 107, 65, 242, 43, 217, 122, 145, 160, + 100, 139, 54, 135, 175, 139, 115, 89, 15, 236, 234, 83, 2, 135, 51, 125, + 63, 168, 184, 235, 148, 68, 132, 124, 166, 171, 53, 68, 94, 187, 31, 68, + 119, 47, 252, 73, 63, 138, 154, 84, 167, 0, 54, 33, 11, 200, 22, 91, 245, + 62, 64, 192, 7, 180, 210, 52, 233, 23, 24, 181, 50, 230, 63, 118, 228, 24, + 1, 242, 75, 62, 196, 222, 122, 154, 227, 125, 89, 73, 112, 100, 154, 249, + 61, 141, 126, 145, 46, 247, 102, 242, 62, 148, 94, 172, 128, 181, 110, 6, + 7, 209, 58, 222, 51, 169, 83, 189, 200, 47, 22, 80, 49, 169, 227, 245, + 165, 24, 96, 152, 228, 14, 252, 199, 193, 148, 46, 84, 49, 248, 198, 7, 0, + 134, 255, 174, 151, 103, 48, 154, 178, 198, 103, 45, 226, 118, 19, 41, 85, + 2, 55, 71, 7, 6, 0, 24, 150, 145, 227, 162, 126, 102, 248, 134, 116, 174, + 215, 217, 166, 160, 140, 129, 21, 220, 131, 110, 242, 94, 249, 103, 151, + 154, 81, 225, 35, 111, 131, 129, 111, 172, 214, 168, 30, 169, 71, 210, 64, + 68, 56, 228, 223, 248, 233, 234, 140, 86, 145, 121, 29, 232, 55, 165, 61, + 175, 147, 66, 33, 92, 6, 209, 241, 149, 73, 77, 9, 104, 2, 154, 247, 92, + 87, 159, 191, 113, 82, 122, 148, 89, 28, 122, 111, 93, 110, 60, 42, 34, + 70, 161, 14, 50, 153, 238, 189, 173, 99, 10, 118, 252, 1, 28, 67, 151, + 114, 46, 78, 181, 78, 233, 183, 6, 254, 57, 29, 53, 118, 175, 80, 97, 156, + 237, 219, 196, 71, 80, 161, 248, 139, 96, 124, 181, 154, 124, 149, 219, + 47, 90, 11, 98, 63, 21, 64, 144, 77, 161, 204, 127, 209, 209, 7, 86, 65, + 39, 142, 251, 183, 43, 227, 120, 155, 72, 70, 204, 89, 227, 199, 203, 28, + 128, 23, 104, 188, 215, 32, 190, 18, 156, 57, 105, 7, 179, 155, 136, 236, + 82, 173, 156, 170, 124, 210, 22, 11, 27, 182, 236, 109, 200, 172, 227, 72, + 37, 1, 175, 9, 214, 227, 23, 141, 169, 215, 77, 134, 76, 229, 169, 241, + 116, 222, 157, 77, 158, 213, 118, 223, 17, 31, 212, 97, 21, 237, 83, 2, + 218, 239, 59, 147, 30, 169, 97, 12, + ]), + + 192: new Uint8Array([ + 129, 16, 61, 38, 99, 56, 226, 139, 71, 251, 211, 15, 91, 152, 159, 219, + 112, 147, 210, 73, 97, 204, 203, 240, 183, 243, 104, 241, 37, 67, 169, + 198, 56, 76, 96, 202, 250, 212, 177, 157, 93, 115, 247, 176, 19, 3, 229, + 102, 75, 200, 252, 222, 197, 58, 31, 44, 123, 151, 9, 191, 88, 123, 35, + 48, 47, 25, 149, 35, 191, 219, 223, 94, 251, 152, 109, 171, 225, 31, 236, + 252, 223, 174, 128, 238, 173, 32, 32, 79, 22, 100, 112, 215, 153, 128, 63, + 158, 247, 18, 215, 81, 247, 208, 91, 28, 223, 222, 170, 9, 135, 210, 143, + 47, 247, 132, 183, 252, 84, 19, 78, 85, 17, 215, 20, 51, 32, 124, 149, + 172, 129, 202, 161, 217, 207, 24, 45, 177, 11, 106, 17, 108, 17, 12, 6, + 62, 90, 132, 2, 54, 96, 90, 30, 239, 216, 173, 76, 67, 7, 221, 62, 124, + 228, 156, 243, 31, 111, 160, 192, 188, 87, 107, 182, 138, 95, 122, 152, + 202, 51, 118, 100, 124, 67, 220, 116, 52, 99, 15, 39, 2, 14, 209, 173, + 119, 88, 6, 174, 106, 236, 150, 28, 189, 112, 161, 224, 186, 58, 110, 91, + 54, 211, 132, 149, 7, 188, 77, 232, 118, 197, 43, 107, 101, 179, 44, 195, + 159, 4, 124, 5, 30, 48, 227, 251, 199, 72, 98, 177, 206, 234, 228, 58, + 191, 150, 28, 211, 29, 182, 138, 141, 249, 152, 142, 244, 203, 210, 128, + 143, 244, 44, 187, 251, 221, 101, 152, 31, 119, 194, 51, 27, 167, 215, + 122, 244, 193, 224, 191, 198, 210, 2, 143, 185, 207, 145, 228, 193, 153, + 207, 119, 167, 75, 145, 43, 17, 1, 42, 146, 164, 21, 15, 164, 221, 216, + 140, 122, 248, 49, 19, 246, 84, 214, 176, 226, 118, 140, 130, 123, 163, + 217, 61, 198, 243, 182, 217, 52, 127, 190, 127, 135, 18, 239, 163, 195, + 102, 136, 227, 128, 38, 244, 49, 208, 229, 249, 126, 157, 100, 72, 246, + 10, 102, 163, 241, 155, 112, 165, 95, 32, 61, 66, 24, 233, 123, 236, 190, + 124, 214, 65, 135, 114, 118, 122, 222, 196, 47, 120, 120, 64, 117, 253, + 165, 28, 17, 152, 104, 119, 10, 53, 140, 109, 79, 246, 246, 28, 104, 228, + 175, 102, 71, 246, 183, 79, 30, 31, 186, 32, 64, 146, 72, 228, 1, 175, + 252, 115, 254, 95, 66, 87, 196, 134, 41, 115, 165, 206, 253, 245, 147, + 137, 163, 230, 235, 238, 77, 218, 74, 157, 65, 97, 43, 198, 130, 190, 195, + 142, 22, 166, 4, 179, 184, 167, 254, 156, 243, 38, 46, 66, 68, 252, 252, + 161, 209, 83, 177, 128, 115, 92, 158, 182, 177, 185, 23, 39, 138, 245, 29, + 216, 17, 178, 142, 225, 135, 8, 115, + ]), + + 256: new Uint8Array([ + 191, 72, 167, 1, 122, 218, 148, 218, 15, 239, 202, 129, 96, 108, 229, 157, + 138, 161, 232, 71, 80, 188, 118, 61, 75, 105, 120, 201, 14, 102, 102, 240, + 111, 131, 180, 83, 95, 73, 2, 138, 205, 56, 9, 137, 227, 235, 73, 71, 200, + 62, 246, 0, 223, 209, 3, 255, 113, 112, 63, 103, 41, 154, 77, 13, 149, 89, + 94, 79, 132, 193, 114, 40, 158, 33, 55, 242, 130, 109, 136, 69, 124, 130, + 150, 40, 69, 211, 224, 154, 209, 243, 65, 58, 230, 253, 31, 21, 72, 102, + 18, 250, 139, 230, 235, 11, 108, 184, 133, 108, 181, 138, 188, 189, 91, + 91, 115, 216, 68, 9, 229, 30, 154, 132, 118, 219, 183, 235, 177, 197, 221, + 58, 13, 90, 126, 198, 74, 87, 162, 226, 7, 51, 184, 15, 209, 81, 86, 138, + 169, 154, 12, 206, 58, 187, 228, 177, 68, 65, 62, 68, 141, 93, 241, 105, + 29, 239, 20, 102, 222, 49, 209, 18, 162, 247, 200, 240, 122, 244, 204, + 148, 67, 58, 118, 164, 95, 230, 68, 242, 203, 138, 145, 132, 6, 224, 206, + 234, 131, 183, 137, 249, 2, 11, 254, 123, 235, 70, 14, 136, 207, 76, 57, + 22, 38, 49, 197, 219, 123, 43, 241, 191, 64, 211, 152, 178, 140, 165, 1, + 189, 52, 79, 184, 213, 56, 215, 182, 27, 27, 70, 243, 101, 255, 50, 108, + 210, 105, 13, 22, 218, 176, 238, 36, 113, 251, 18, 218, 138, 214, 193, 21, + 122, 224, 125, 118, 134, 161, 174, 130, 86, 233, 149, 151, 33, 31, 88, 63, + 91, 63, 209, 145, 158, 109, 42, 176, 43, 23, 151, 49, 101, 199, 35, 101, + 158, 139, 198, 219, 209, 125, 221, 205, 99, 69, 142, 165, 139, 110, 220, + 184, 226, 238, 149, 161, 175, 171, 167, 170, 65, 19, 156, 166, 219, 231, + 87, 20, 226, 58, 210, 134, 110, 160, 176, 118, 250, 73, 86, 213, 116, 53, + 114, 24, 101, 34, 185, 59, 237, 47, 39, 206, 67, 12, 74, 236, 130, 7, 249, + 217, 203, 245, 122, 14, 230, 53, 203, 126, 93, 131, 51, 2, 0, 231, 161, + 111, 42, 126, 173, 121, 80, 179, 59, 186, 133, 236, 252, 188, 149, 99, + 221, 182, 55, 5, 38, 83, 132, 43, 123, 233, 174, 208, 140, 165, 77, 1, + 202, 46, 6, 183, 207, 246, 125, 37, 110, 226, 61, 155, 194, 198, 153, 107, + 1, 8, 0, 23, 124, 18, 4, 144, 235, 146, 77, 220, 123, 152, 114, 219, 127, + 59, 126, 10, 79, 106, 198, 11, 27, 111, 11, 155, 1, 137, 38, 74, 3, 248, + 225, 221, 203, 86, 4, 148, 25, 88, 144, 185, 38, 114, 139, 48, 74, 82, + 172, 36, 115, 193, 223, 220, 144, 69, 91, 5, 83, 56, 138, 63, + ]), + }; + + return { + plaintext, + keyBytes, + iv, + additionalData, + tag, + tag_with_empty_ad, + ciphertext, + }; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_vectors.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_vectors.js new file mode 100644 index 0000000000000..965fe9564d461 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/encrypt_decrypt/aes_gcm_vectors.js @@ -0,0 +1,76 @@ +// aes_gcm_vectors.js + +// The following function returns an array of test vectors +// for the subtleCrypto encrypt method. +// +// Each test vector has the following fields: +// name - a unique name for this vector +// keyBuffer - an arrayBuffer with the key data in raw form +// key - a CryptoKey object for the keyBuffer. INITIALLY null! You must fill this in first to use it! +// algorithm - the value of the AlgorithmIdentifier parameter to provide to encrypt +// plaintext - the text to encrypt +// result - the expected result (usually just ciphertext, sometimes with added authentication) +function getTestVectors() { + const { + plaintext, + keyBytes, + iv, + additionalData, + tag, + tag_with_empty_ad, + ciphertext, + } = getFixtures(); + + var keyLengths = [128, 192, 256]; + var tagLengths = [32, 64, 96, 104, 112, 120, 128]; + + // All the scenarios that should succeed, if the key has "encrypt" usage + var passing = []; + keyLengths.forEach(function(keyLength) { + tagLengths.forEach(function(tagLength) { + var byteCount = tagLength / 8; + + var result = new Uint8Array(ciphertext[keyLength].byteLength + byteCount); + result.set(ciphertext[keyLength], 0); + result.set(tag[keyLength].slice(0, byteCount), ciphertext[keyLength].byteLength); + passing.push({ + name: "AES-GCM " + keyLength.toString() + "-bit key, " + tagLength.toString() + "-bit tag, " + (iv.byteLength << 3).toString() + "-bit iv", + keyBuffer: keyBytes[keyLength], + key: null, + algorithm: {name: "AES-GCM", iv: iv, additionalData: additionalData, tagLength: tagLength}, + plaintext: plaintext, + result: result + }); + + var noadresult = new Uint8Array(ciphertext[keyLength].byteLength + byteCount); + noadresult.set(ciphertext[keyLength], 0); + noadresult.set(tag_with_empty_ad[keyLength].slice(0, byteCount), ciphertext[keyLength].byteLength); + passing.push({ + name: "AES-GCM " + keyLength.toString() + "-bit key, no additional data, " + tagLength.toString() + "-bit tag, " + (iv.byteLength << 3).toString() + "-bit iv", + keyBuffer: keyBytes[keyLength], + key: null, + algorithm: {name: "AES-GCM", iv: iv, tagLength: tagLength}, + plaintext: plaintext, + result: noadresult + }); + }); + }); + + // Scenarios that should fail because of a bad tag length, causing an OperationError + var failing = []; + keyLengths.forEach(function(keyLength) { + // First, make some tests for bad tag lengths + [24, 48, 72, 95, 129].forEach(function(badTagLength) { + failing.push({ + name: "AES-GCM " + keyLength.toString() + "-bit key, " + (iv.byteLength << 3).toString() + "-bit iv, " + "illegal tag length " + badTagLength.toString() + "-bits", + keyBuffer: keyBytes[keyLength], + key: null, + algorithm: {name: "AES-GCM", iv: iv, additionalData: additionalData, tagLength: badTagLength}, + plaintext: plaintext, + result: ciphertext[keyLength] + }); + }); + }); + + return {passing: passing, failing: failing, decryptionFailing: []}; +} From aa5207199f9cde9aca162ce69b19ebcc4601c3b1 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Mon, 16 Dec 2024 19:28:36 +0900 Subject: [PATCH 208/237] Tests: Import remaining HTML-AAM tests (no code) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change imports the remaining HTML-AAM tests from WPT that haven’t yet been imported in any previous PRs — giving us complete in-tree regression-testing coverage for all available WPT tests for the requirements in the HTML-AAM spec. --- .../wpt-import/html-aam/fragile/area-role.txt | 7 +++++ .../html-aam/fragile/optgroup-role.txt | 6 ++++ .../html-aam/fragile/area-role.html | 26 ++++++++++++++++ .../html-aam/fragile/optgroup-role.html | 30 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/area-role.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/optgroup-role.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/area-role.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/optgroup-role.html diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/area-role.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/area-role.txt new file mode 100644 index 0000000000000..98b72212646b6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/area-role.txt @@ -0,0 +1,7 @@ +Harness status: OK + +Found 2 tests + +2 Pass +Pass el-area +Pass el-area-no-href \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/optgroup-role.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/optgroup-role.txt new file mode 100644 index 0000000000000..248b77c8b279f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/fragile/optgroup-role.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass el-optgroup \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/area-role.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/area-role.html new file mode 100644 index 0000000000000..5870adae10f85 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/area-role.html @@ -0,0 +1,26 @@ + + + + HTMLAreaElement Role Verification Tests + + + + + + + + + + + x + x + +x + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/optgroup-role.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/optgroup-role.html new file mode 100644 index 0000000000000..47cef2f620377 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/fragile/optgroup-role.html @@ -0,0 +1,30 @@ + + + + HTMLOptGroupElement Role Verification Tests + + + + + + + + + + + + + + + \ No newline at end of file From a5bdc56063036cfd91adedecf003d7dcfb70c131 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 15 Dec 2024 13:06:11 +1300 Subject: [PATCH 209/237] LibWeb/HTML: Make ErrorEvent::create a trusted event Matching DOM::Event. --- Libraries/LibWeb/HTML/ErrorEvent.cpp | 6 ++-- .../processing-model-2/addEventListener.txt | 6 ++++ .../processing-model-2/addEventListener.html | 32 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.html diff --git a/Libraries/LibWeb/HTML/ErrorEvent.cpp b/Libraries/LibWeb/HTML/ErrorEvent.cpp index 9005bbb4798dd..76a19b02d78b1 100644 --- a/Libraries/LibWeb/HTML/ErrorEvent.cpp +++ b/Libraries/LibWeb/HTML/ErrorEvent.cpp @@ -14,12 +14,14 @@ GC_DEFINE_ALLOCATOR(ErrorEvent); GC::Ref ErrorEvent::create(JS::Realm& realm, FlyString const& event_name, ErrorEventInit const& event_init) { - return realm.create(realm, event_name, event_init); + auto event = realm.create(realm, event_name, event_init); + event->set_is_trusted(true); + return event; } WebIDL::ExceptionOr> ErrorEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, ErrorEventInit const& event_init) { - return create(realm, event_name, event_init); + return realm.create(realm, event_name, event_init); } ErrorEvent::ErrorEvent(JS::Realm& realm, FlyString const& event_name, ErrorEventInit const& event_init) diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.txt b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.txt new file mode 100644 index 0000000000000..41bf5503d2cf5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass window.onerror - addEventListener \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.html b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.html new file mode 100644 index 0000000000000..f3e1986e3807f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/scripting/processing-model-2/addEventListener.html @@ -0,0 +1,32 @@ + + + + window.onerror - addEventListener + + + + +
    + + + + + + From e5436ce5935895e0d16be57261642f9313b94f15 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 15 Dec 2024 13:15:30 +1300 Subject: [PATCH 210/237] LibWeb/HTML: Remove uneeded FIXME about use of USVString An AK::String works fine for a USVString as a USVString is just a more strict version of DOMString. Maybe we will have a different String type for it in the future, but for now using an AK::String is fine and we do not need this FIXME. --- Libraries/LibWeb/HTML/ErrorEvent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/HTML/ErrorEvent.h b/Libraries/LibWeb/HTML/ErrorEvent.h index 50c6a691924ac..59a87f9623793 100644 --- a/Libraries/LibWeb/HTML/ErrorEvent.h +++ b/Libraries/LibWeb/HTML/ErrorEvent.h @@ -14,7 +14,7 @@ namespace Web::HTML { // https://html.spec.whatwg.org/multipage/webappapis.html#erroreventinit struct ErrorEventInit : public DOM::EventInit { String message; - String filename; // FIXME: This should be a USVString. + String filename; u32 lineno { 0 }; u32 colno { 0 }; JS::Value error { JS::js_null() }; From 203267fcc24aaa9aa35bcb2cd5a181150ff260f8 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 16 Dec 2024 08:24:05 -0500 Subject: [PATCH 211/237] LibWeb: Do not worry about small OOM in JsonWebKey::parse We do not concern ourselves with small OOM handling any longer. But the main point of this patch is that we are for some reason getting a build error with clang-19 here about ignoring a nodiscard return type inside JsonArray::try_for_each. --- Libraries/LibWeb/Crypto/CryptoBindings.cpp | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Crypto/CryptoBindings.cpp b/Libraries/LibWeb/Crypto/CryptoBindings.cpp index bce65d231baa3..5509132c6b31a 100644 --- a/Libraries/LibWeb/Crypto/CryptoBindings.cpp +++ b/Libraries/LibWeb/Crypto/CryptoBindings.cpp @@ -14,19 +14,21 @@ #include namespace Web::Bindings { -#define JWK_PARSE_STRING_PROPERTY(name) \ - if (json_object.has_string(#name##sv)) { \ - key.name = TRY_OR_THROW_OOM(vm, String::from_byte_string(*json_object.get_byte_string(#name##sv))); \ + +#define JWK_PARSE_STRING_PROPERTY(name) \ + if (json_object.has_string(#name##sv)) { \ + key.name = MUST(String::from_byte_string(*json_object.get_byte_string(#name##sv))); \ } JS::ThrowCompletionOr JsonWebKey::parse(JS::Realm& realm, ReadonlyBytes data) { auto& vm = realm.vm(); + // 1. Let data be the sequence of bytes to be parsed. // 2. Let json be the Unicode string that results from interpreting data according to UTF-8. // 3. Convert json to UTF-16. - auto json = TRY_OR_THROW_OOM(vm, String::from_utf8(data)); + auto json = MUST(String::from_utf8(data)); // 4. Let result be the object literal that results from executing the JSON.parse internal function // in the context of a new global object, with text argument set to a JavaScript String containing json. @@ -39,7 +41,7 @@ JS::ThrowCompletionOr JsonWebKey::parse(JS::Realm& realm, ReadonlyBy return vm.throw_completion("JSON value is not an object"_string); } - auto& json_object = json_value.as_object(); + auto const& json_object = json_value.as_object(); // 5. Let key be the result of converting result to the IDL dictionary type of JsonWebKey. JsonWebKey key {}; @@ -61,15 +63,13 @@ JS::ThrowCompletionOr JsonWebKey::parse(JS::Realm& realm, ReadonlyBy key.ext = json_object.get_bool("ext"sv); - if (json_object.has_array("key_ops"sv)) { - auto key_ops = *json_object.get_array("key_ops"sv); + if (auto key_ops = json_object.get_array("key_ops"sv); key_ops.has_value()) { key.key_ops = Vector {}; - TRY_OR_THROW_OOM(vm, key.key_ops->try_ensure_capacity(key_ops.size())); + key.key_ops->ensure_capacity(key_ops->size()); - TRY(key_ops.try_for_each([&key, &vm](auto value) -> JS::Completion { - key.key_ops->append(TRY_OR_THROW_OOM(vm, String::from_byte_string(value.as_string()))); - return {}; - })); + key_ops->for_each([&](auto const& value) { + key.key_ops->append(MUST(String::from_byte_string(value.as_string()))); + }); } if (json_object.has("oth"sv)) From 1b165d887bec38ccdd453ba06efc65951b7d8ea1 Mon Sep 17 00:00:00 2001 From: sideshowbarker Date: Mon, 16 Dec 2024 18:54:50 +0900 Subject: [PATCH 212/237] =?UTF-8?q?LibWeb:=20Support=20the=20ARIA=20?= =?UTF-8?q?=E2=80=9Csectionheader=E2=80=9D=20&=20=E2=80=9Csectionfooter?= =?UTF-8?q?=E2=80=9D=20roles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Libraries/LibWeb/ARIA/AriaRoles.json | 92 +++++++++++++++++++ Libraries/LibWeb/ARIA/RoleType.cpp | 4 + Libraries/LibWeb/ARIA/Roles.h | 2 + Libraries/LibWeb/HTML/HTMLElement.cpp | 14 ++- .../html-aam/roles-contextual.tentative.txt | 9 ++ .../html-aam/roles-contextual.tentative.html | 36 ++++++++ 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.tentative.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.tentative.html diff --git a/Libraries/LibWeb/ARIA/AriaRoles.json b/Libraries/LibWeb/ARIA/AriaRoles.json index 4bd5ec3453896..bb11cf378c9c6 100644 --- a/Libraries/LibWeb/ARIA/AriaRoles.json +++ b/Libraries/LibWeb/ARIA/AriaRoles.json @@ -496,6 +496,98 @@ "childrenArePresentational": false, "implicitValueForRole": {} }, + "SectionFooter": { + "specLink": "https://w3c.github.io/aria/#sectionfooter", + "description": "A set of user interface objects and information representing information about its closest ancestral content group.", + "superClassRoles": [ + "Section" + ], + "supportedStates": [ + "aria-busy", + "aria-current", + "aria-disabled", + "aria-grabbed", + "aria-hidden", + "aria-invalid" + ], + "supportedProperties": [ + "aria-atomic", + "aria-braillelabel", + "aria-brailleroledescription", + "aria-controls", + "aria-describedby", + "aria-description", + "aria-details", + "aria-dropeffect", + "aria-dropeffect", + "aria-errormessage", + "aria-flowto", + "aria-haspopup", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-live", + "aria-owns", + "aria-relevant", + "aria-roledescription" + ], + "requiredStates": [], + "requiredProperties": [], + "prohibitedStates": [], + "prohibitedProperties": [], + "requiredContextRoles": [], + "requiredOwnedElements": [], + "nameFromSource": "Author", + "accessibleNameRequired": false, + "childrenArePresentational": false, + "implicitValueForRole": {} + }, + "SectionHeader": { + "specLink": "https://w3c.github.io/aria/#sectionheader", + "description": "A set of user interface objects and information that represents a collection of introductory items for the element's closest ancestral content group.", + "superClassRoles": [ + "Section" + ], + "supportedStates": [ + "aria-busy", + "aria-current", + "aria-disabled", + "aria-grabbed", + "aria-hidden", + "aria-invalid" + ], + "supportedProperties": [ + "aria-atomic", + "aria-braillelabel", + "aria-brailleroledescription", + "aria-controls", + "aria-describedby", + "aria-description", + "aria-details", + "aria-dropeffect", + "aria-dropeffect", + "aria-errormessage", + "aria-flowto", + "aria-haspopup", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-live", + "aria-owns", + "aria-relevant", + "aria-roledescription" + ], + "requiredStates": [], + "requiredProperties": [], + "prohibitedStates": [], + "prohibitedProperties": [], + "requiredContextRoles": [], + "requiredOwnedElements": [], + "nameFromSource": "Author", + "accessibleNameRequired": false, + "childrenArePresentational": false, + "implicitValueForRole": {} + }, "Input": { "specLink": "https://www.w3.org/TR/wai-aria-1.2/#input", "description": "A generic type of widget that allows user input.", diff --git a/Libraries/LibWeb/ARIA/RoleType.cpp b/Libraries/LibWeb/ARIA/RoleType.cpp index ceba11dc0fcdc..891e15bb85c78 100644 --- a/Libraries/LibWeb/ARIA/RoleType.cpp +++ b/Libraries/LibWeb/ARIA/RoleType.cpp @@ -282,6 +282,10 @@ ErrorOr> RoleType::build_role_object(Role role, bool foc return adopt_nonnull_own_or_enomem(new (nothrow) Search(data)); case Role::searchbox: return adopt_nonnull_own_or_enomem(new (nothrow) SearchBox(data)); + case Role::sectionfooter: + return adopt_nonnull_own_or_enomem(new (nothrow) SectionFooter(data)); + case Role::sectionheader: + return adopt_nonnull_own_or_enomem(new (nothrow) SectionHeader(data)); case Role::separator: if (focusable) return adopt_nonnull_own_or_enomem(new (nothrow) FocusableSeparator(data)); diff --git a/Libraries/LibWeb/ARIA/Roles.h b/Libraries/LibWeb/ARIA/Roles.h index 1ce01d8cc870b..193fe9ac66cb4 100644 --- a/Libraries/LibWeb/ARIA/Roles.h +++ b/Libraries/LibWeb/ARIA/Roles.h @@ -82,7 +82,9 @@ namespace Web::ARIA { __ENUMERATE_ARIA_ROLE(search) \ __ENUMERATE_ARIA_ROLE(searchbox) \ __ENUMERATE_ARIA_ROLE(section) \ + __ENUMERATE_ARIA_ROLE(sectionfooter) \ __ENUMERATE_ARIA_ROLE(sectionhead) \ + __ENUMERATE_ARIA_ROLE(sectionheader) \ __ENUMERATE_ARIA_ROLE(select) \ __ENUMERATE_ARIA_ROLE(separator) \ __ENUMERATE_ARIA_ROLE(slider) \ diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 4321db9997a3a..35a412fdcb0e0 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -766,10 +766,16 @@ Optional HTMLElement::default_role() const // complementary, main, navigation or region then (footer) role=contentinfo (header) role=banner. Otherwise, // role=generic. for (auto const* ancestor = parent_element(); ancestor; ancestor = ancestor->parent_element()) { - if (first_is_one_of(ancestor->local_name(), TagNames::article, TagNames::aside, TagNames::main, TagNames::nav, TagNames::section)) - return ARIA::Role::generic; - if (first_is_one_of(ancestor->role_or_default(), ARIA::Role::article, ARIA::Role::complementary, ARIA::Role::main, ARIA::Role::navigation, ARIA::Role::region)) - return ARIA::Role::generic; + if (first_is_one_of(ancestor->local_name(), TagNames::article, TagNames::aside, TagNames::main, TagNames::nav, TagNames::section)) { + if (local_name() == TagNames::footer) + return ARIA::Role::sectionfooter; + return ARIA::Role::sectionheader; + } + if (first_is_one_of(ancestor->role_or_default(), ARIA::Role::article, ARIA::Role::complementary, ARIA::Role::main, ARIA::Role::navigation, ARIA::Role::region)) { + if (local_name() == TagNames::footer) + return ARIA::Role::sectionfooter; + return ARIA::Role::sectionheader; + } } // then (footer) role=contentinfo. if (local_name() == TagNames::footer) diff --git a/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.tentative.txt b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.tentative.txt new file mode 100644 index 0000000000000..3a8f388f03b12 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-contextual.tentative.txt @@ -0,0 +1,9 @@ +Harness status: OK + +Found 4 tests + +4 Pass +Pass el-footer +Pass el-footer-ancestormain +Pass el-header +Pass el-header-ancestormain \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.tentative.html b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.tentative.html new file mode 100644 index 0000000000000..6119efa40a9a2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html-aam/roles-contextual.tentative.html @@ -0,0 +1,36 @@ + + + + Tentative: HTML-AAM Contextual-Specific Role Verification Tests + + + + + + + + + + + +
    +
    x
    +
    + +
    +
    x
    +
    + + + + + \ No newline at end of file From 09a55e56ee34d2fc2b0fdd8d3217e487254d4b78 Mon Sep 17 00:00:00 2001 From: InvalidUsernameException Date: Sun, 8 Dec 2024 21:23:15 +0100 Subject: [PATCH 213/237] LibWeb: Add support for `object-fit: scale-down` --- Libraries/LibWeb/Painting/ImagePaintable.cpp | 13 ++- .../expected/object-fit-scale-down-ref.html | 83 +++++++++++++++++++ .../Ref/input/object-fit-scale-down.html | 27 ++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Ref/expected/object-fit-scale-down-ref.html create mode 100644 Tests/LibWeb/Ref/input/object-fit-scale-down.html diff --git a/Libraries/LibWeb/Painting/ImagePaintable.cpp b/Libraries/LibWeb/Painting/ImagePaintable.cpp index b888011d092c8..9ec6bc4337539 100644 --- a/Libraries/LibWeb/Painting/ImagePaintable.cpp +++ b/Libraries/LibWeb/Painting/ImagePaintable.cpp @@ -75,11 +75,21 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const auto bitmap_aspect_ratio = (float)bitmap_rect.height() / bitmap_rect.width(); auto image_aspect_ratio = (float)image_rect.height().value() / image_rect.width().value(); + // FIXME: Scale may be wrong if device-pixels != css-pixels. auto scale_x = 0.0f; auto scale_y = 0.0f; Gfx::IntRect bitmap_intersect = bitmap_rect; + // https://drafts.csswg.org/css-images/#the-object-fit auto object_fit = m_is_svg_image ? CSS::ObjectFit::Contain : computed_values().object_fit(); + if (object_fit == CSS::ObjectFit::ScaleDown) { + if (bitmap_rect.width() > image_int_rect.width() || bitmap_rect.height() > image_int_rect.height()) { + object_fit = CSS::ObjectFit::Contain; + } else { + object_fit = CSS::ObjectFit::None; + } + } + switch (object_fit) { case CSS::ObjectFit::Fill: scale_x = (float)image_int_rect.width() / bitmap_rect.width(); @@ -106,7 +116,7 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const } break; case CSS::ObjectFit::ScaleDown: - // FIXME: Implement + VERIFY_NOT_REACHED(); // handled outside the switch-case case CSS::ObjectFit::None: scale_x = 1; scale_y = 1; @@ -122,6 +132,7 @@ void ImagePaintable::paint(PaintContext& context, PaintPhase phase) const bitmap_intersect.set_x((bitmap_rect.width() - bitmap_intersect.width()) / 2); bitmap_intersect.set_y((bitmap_rect.height() - bitmap_intersect.height()) / 2); + // https://drafts.csswg.org/css-images/#the-object-position auto const& object_position = computed_values().object_position(); auto offset_x = 0; diff --git a/Tests/LibWeb/Ref/expected/object-fit-scale-down-ref.html b/Tests/LibWeb/Ref/expected/object-fit-scale-down-ref.html new file mode 100644 index 0000000000000..ab7ca751a1061 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/object-fit-scale-down-ref.html @@ -0,0 +1,83 @@ + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/Tests/LibWeb/Ref/input/object-fit-scale-down.html b/Tests/LibWeb/Ref/input/object-fit-scale-down.html new file mode 100644 index 0000000000000..f7074bee17305 --- /dev/null +++ b/Tests/LibWeb/Ref/input/object-fit-scale-down.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + From 0a02eb639d6e3c80a86f6109b552eb799a22d590 Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Fri, 6 Dec 2024 13:05:29 +0000 Subject: [PATCH 214/237] LibWeb: Implement popover's close watcher Auto popovers now correctly establish a close watcher when shown. This means popovers now correctly close with an escape key press. Also correctly hide open popovers when removed from the document. --- Libraries/LibWeb/HTML/HTMLElement.cpp | 35 ++++++++++++++++++++++++--- Libraries/LibWeb/HTML/HTMLElement.h | 5 ++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 35a412fdcb0e0..4189adf3015f3 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +67,7 @@ void HTMLElement::visit_edges(Cell::Visitor& visitor) visitor.visit(m_labels); visitor.visit(m_attached_internals); visitor.visit(m_popover_invoker); + visitor.visit(m_popover_close_watcher); } // https://html.spec.whatwg.org/multipage/dom.html#dom-dir @@ -1051,9 +1054,20 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except // FIXME: 11.5. If originalType is not equal to the value of element's popover attribute, then throw a "InvalidStateError" DOMException. // FIXME: 11.6. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. // FIXME: 11.7. If the result of running topmost auto popover on document is null, then set shouldRestoreFocus to true. - // FIXME: 11.8. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with: + // 11.8. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with: + m_popover_close_watcher = CloseWatcher::establish(*document.window()); // - cancelAction being to return true. + // We simply don't add an event listener for the cancel action. // - closeAction being to hide a popover given element, true, true, and false. + auto close_callback_function = JS::NativeFunction::create( + realm(), [this](JS::VM&) { + MUST(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No)); + + return JS::js_undefined(); + }, + 0, "", &realm()); + auto close_callback = realm().heap().allocate(*close_callback_function, realm()); + m_popover_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback)); } // FIXME: 12. Set element's previously focused element to null. @@ -1116,9 +1130,13 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 6.1. If nestedHide is false, then set element's popover showing or hiding to false. if (nested_hide) m_popover_showing_or_hiding = false; - // FIXME: 6.2. If element's popover close watcher is not null, then: - // FIXME: 6.2.1. Destroy element's popover close watcher. - // FIXME: 6.2.2. Set element's popover close watcher to null. + // 6.2. If element's popover close watcher is not null, then: + if (m_popover_close_watcher) { + // 6.2.1. Destroy element's popover close watcher. + m_popover_close_watcher->destroy(); + // 6.2.2. Set element's popover close watcher to null. + m_popover_close_watcher = nullptr; + } }; // 7. If element's popover attribute is in the auto state, then: @@ -1275,6 +1293,15 @@ void HTMLElement::did_lose_focus() document().editing_host_manager()->set_active_contenteditable_element(nullptr); } +void HTMLElement::removed_from(Node* old_parent) +{ + Element::removed_from(old_parent); + + // If removedNode's popover attribute is not in the no popover state, then run the hide popover algorithm given removedNode, false, false, and false. + if (popover().has_value()) + MUST(hide_popover(FocusPreviousElement::No, FireEvents::No, ThrowExceptions::No)); +} + // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel String HTMLElement::access_key_label() const { diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index c44bef4327e89..bb85e985ff9af 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -116,6 +116,8 @@ class HTMLElement WebIDL::ExceptionOr set_popover(Optional value); Optional popover() const; + virtual void removed_from(Node*) override; + enum class PopoverVisibilityState { Hidden, Showing, @@ -181,6 +183,9 @@ class HTMLElement // https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute:toggle-task-tracker Optional m_popover_toggle_task_tracker; + + // https://html.spec.whatwg.org/multipage/popover.html#popover-close-watcher + GC::Ptr m_popover_close_watcher; }; } From 1d94d678b375c4316cc3375b5a3fc1197de8d3b1 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Mon, 16 Dec 2024 19:13:26 +0100 Subject: [PATCH 215/237] LibCrypto: Implement AES-KW Add the AES-KW (Key Wrap) implementation as of https://www.rfc-editor.org/rfc/rfc3394#section-4.2. Tests are taken from section 4 of RFC3394. --- Libraries/LibCrypto/Cipher/AES.h | 2 + Libraries/LibCrypto/Cipher/Mode/KW.h | 132 ++++++++++++++++++++++ Tests/LibCrypto/TestAES.cpp | 157 ++++++++++++++++++++++++++- 3 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibCrypto/Cipher/Mode/KW.h diff --git a/Libraries/LibCrypto/Cipher/AES.h b/Libraries/LibCrypto/Cipher/AES.h index eb0dcec2ae28c..ab2b7b91d7309 100644 --- a/Libraries/LibCrypto/Cipher/AES.h +++ b/Libraries/LibCrypto/Cipher/AES.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace Crypto::Cipher { @@ -96,6 +97,7 @@ class AESCipher final : public Cipher { using CBCMode = CBC; using CTRMode = CTR; using GCMMode = GCM; + using KWMode = KW; constexpr static size_t BlockSizeInBits = BlockType::BlockSizeInBits; diff --git a/Libraries/LibCrypto/Cipher/Mode/KW.h b/Libraries/LibCrypto/Cipher/Mode/KW.h new file mode 100644 index 0000000000000..866b56607e936 --- /dev/null +++ b/Libraries/LibCrypto/Cipher/Mode/KW.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Crypto::Cipher { + +template +class KW : public Mode { +public: + constexpr static size_t IVSizeInBits = 128; + constexpr static u8 default_iv[8] = { 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 }; + + virtual ~KW() = default; + template + explicit constexpr KW(Args... args) + : Mode(args...) + { + } + + virtual ByteString class_name() const override + { + StringBuilder builder; + builder.append(this->cipher().class_name()); + builder.append("_KW"sv); + return builder.to_byte_string(); + } + + virtual size_t IV_length() const override + { + return IVSizeInBits / 8; + } + + // FIXME: This overload throws away the validation, think up a better way to return more than a single bytebuffer. + virtual void encrypt(ReadonlyBytes in, Bytes& out, [[maybe_unused]] ReadonlyBytes ivec = {}, [[maybe_unused]] Bytes* ivec_out = nullptr) override + { + this->wrap(in, out); + } + + virtual void decrypt(ReadonlyBytes in, Bytes& out, [[maybe_unused]] ReadonlyBytes ivec = {}) override + { + this->unwrap(in, out); + } + + void wrap(ReadonlyBytes in, Bytes& out) + { + // The plaintext consists of n 64-bit blocks, containing the key data being wrapped. + VERIFY(in.size() % 8 == 0); + VERIFY(out.size() >= in.size() + 8); + + auto& cipher = this->cipher(); + + auto iv = MUST(ByteBuffer::copy(default_iv, 8)); + auto data = MUST(ByteBuffer::copy(in)); + auto data_blocks = data.size() / 8; + + // For j = 0 to 5 + for (size_t j = 0; j < 6; ++j) { + // For i=1 to n + for (size_t i = 0; i < data_blocks; ++i) { + // B = AES(K, A | R[i]) + m_cipher_block.bytes().overwrite(0, iv.data(), 8); + m_cipher_block.bytes().overwrite(8, data.data() + i * 8, 8); + cipher.encrypt_block(m_cipher_block, m_cipher_block); + + // A = MSB(64, B) ^ t where t = (n*j)+i + u64 a = AK::convert_between_host_and_big_endian(ByteReader::load64(m_cipher_block.bytes().data())) ^ ((data_blocks * j) + i + 1); + ByteReader::store(iv.data(), AK::convert_between_host_and_big_endian(a)); + + // R[i] = LSB(64, B) + data.overwrite(i * 8, m_cipher_block.bytes().data() + 8, 8); + } + } + + out.overwrite(0, iv.data(), 8); + out.overwrite(8, data.data(), data.size()); + } + + VerificationConsistency unwrap(ReadonlyBytes in, Bytes& out) + { + // The inputs to the unwrap process are the KEK and (n+1) 64-bit blocks + // of ciphertext consisting of previously wrapped key. + VERIFY(in.size() % 8 == 0); + VERIFY(in.size() > 8); + + // It returns n blocks of plaintext consisting of the n 64 - bit blocks of the decrypted key data. + VERIFY(out.size() >= in.size() - 8); + + auto& cipher = this->cipher(); + + auto iv = MUST(ByteBuffer::copy(in.slice(0, 8))); + auto data = MUST(ByteBuffer::copy(in.slice(8, in.size() - 8))); + auto data_blocks = data.size() / 8; + + // For j = 5 to 0 + for (size_t j = 6; j > 0; --j) { + // For i = n to 1 + for (size_t i = data_blocks; i > 0; --i) { + // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i + u64 a = AK::convert_between_host_and_big_endian(ByteReader::load64(iv.data())) ^ ((data_blocks * (j - 1)) + i); + ByteReader::store(m_cipher_block.bytes().data(), AK::convert_between_host_and_big_endian(a)); + m_cipher_block.bytes().overwrite(8, data.data() + ((i - 1) * 8), 8); + cipher.decrypt_block(m_cipher_block, m_cipher_block); + + // A = MSB(64, B) + iv.overwrite(0, m_cipher_block.bytes().data(), 8); + + // R[i] = LSB(64, B) + data.overwrite((i - 1) * 8, m_cipher_block.bytes().data() + 8, 8); + } + } + + if (ReadonlyBytes { default_iv, 8 } != iv.bytes()) + return VerificationConsistency::Inconsistent; + + out.overwrite(0, data.data(), data.size()); + return VerificationConsistency::Consistent; + } + +private: + typename T::BlockType m_cipher_block {}; +}; + +} diff --git a/Tests/LibCrypto/TestAES.cpp b/Tests/LibCrypto/TestAES.cpp index cb9f0d14e8aa5..c84c033d49b10 100644 --- a/Tests/LibCrypto/TestAES.cpp +++ b/Tests/LibCrypto/TestAES.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include #include @@ -572,3 +571,159 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_multiple_blocks_with_aad) EXPECT(memcmp(result_pt, out.data(), out.size()) == 0); EXPECT_EQ(consistency, Crypto::VerificationConsistency::Consistent); } + +TEST_CASE(test_AES_KW_encrypt_128bits_with_128bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b, 128, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x1F\xA6\x8B\x0A\x81\x12\xB4\x47\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82\x9D\x3E\x86\x23\x71\xD2\xCF\xE5"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_128bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b, 128, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x1F\xA6\x8B\x0A\x81\x12\xB4\x47\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82\x9D\x3E\x86\x23\x71\xD2\xCF\xE5"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_128bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x96\x77\x8B\x25\xAE\x6C\xA4\x35\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x96\x77\x8B\x25\xAE\x6C\xA4\x35\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_128bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2\x63\xE9\x77\x79\x05\x81\x8A\x2A\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2\x63\xE9\x77\x79\x05\x81\x8A\x2A\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_192bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x03\x1D\x33\x26\x4E\x15\xD3\x32\x68\xF2\x4E\xC2\x60\x74\x3E\xDC\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93\x6B\xA8\x14\x91\x5C\x67\x62\xD2"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_192bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + auto in = "\x03\x1D\x33\x26\x4E\x15\xD3\x32\x68\xF2\x4E\xC2\x60\x74\x3E\xDC\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93\x6B\xA8\x14\x91\x5C\x67\x62\xD2"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_192bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4\x76\x9C\x8B\x80\xA3\x2C\xB8\x95\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_192bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + auto in = "\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4\x76\x9C\x8B\x80\xA3\x2C\xB8\x95\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_256bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x28\xC9\xF4\x04\xC4\xB8\x10\xF4\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26\x3F\x57\x86\xE2\xD8\x0E\xD3\x26\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B\xFB\x98\x8B\x9B\x7A\x02\xDD\x21"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_256bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b; + auto in = "\x28\xC9\xF4\x04\xC4\xB8\x10\xF4\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26\x3F\x57\x86\xE2\xD8\x0E\xD3\x26\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B\xFB\x98\x8B\x9B\x7A\x02\xDD\x21"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} From 94374f0d1967e2eb40cb6b8466eb7597357692a5 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Mon, 16 Dec 2024 19:38:10 +0100 Subject: [PATCH 216/237] LibWeb: Implement AES-KW in WebCryptoAPI Add support for AES-KW for key wrapping/unwrapping. Very similar implementation to other AES modes. Added generic tests for symmetric import and specific AES-KW ones. Adds ~400 test passes on WPT. Now we do better than Firefox in `WebCryptoAPI/wrapKey_unwrapKey`! --- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 298 ++++++++++ Libraries/LibWeb/Crypto/CryptoAlgorithms.h | 18 + Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 12 +- .../generateKey/failures_AES-KW.https.any.txt | 522 ++++++++++++++++++ .../successes_AES-KW.https.any.txt | 77 +++ .../symmetric_importKey.https.any.txt | 395 +++++++++++++ .../wrapKey_unwrapKey.https.any.txt | 43 +- .../failures_AES-KW.https.any.html | 17 + .../generateKey/failures_AES-KW.https.any.js | 5 + .../successes_AES-KW.https.any.html | 18 + .../generateKey/successes_AES-KW.https.any.js | 6 + .../symmetric_importKey.https.any.html | 16 + .../symmetric_importKey.https.any.js | 222 ++++++++ 13 files changed, 1640 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.js diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 6e1962be80b06..2fd16f831794d 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -2183,6 +2183,304 @@ WebIDL::ExceptionOr, GC::Ref>> AesGcm: return { key }; } +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr> AesKw::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& key_usages) +{ + // 1. If usages contains an entry which is not one of "wrapKey" or "unwrapKey", then throw a SyntaxError. + for (auto& usage : key_usages) { + if (usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + ByteBuffer data; + + // 2. If format is "raw": + if (format == Bindings::KeyFormat::Raw) { + // 1. Let data be the octet string contained in keyData. + data = key_data.get(); + + // 2. If the length in bits of data is not 128, 192 or 256 then throw a DataError. + auto length_in_bits = data.size() * 8; + if (length_in_bits != 128 && length_in_bits != 192 && length_in_bits != 256) { + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key length '{}' bits (must be either 128, 192, or 256 bits)", length_in_bits))); + } + } + + // 2. If format is "jwk": + else if (format == Bindings::KeyFormat::Jwk) { + // 1. -> If keyData is a JsonWebKey dictionary: + // Let jwk equal keyData. + // -> Otherwise: + // Throw a DataError. + if (!key_data.has()) + return WebIDL::DataError::create(m_realm, "keyData is not a JsonWebKey dictionary"_string); + + auto& jwk = key_data.get(); + + // 2. If the kty field of jwk is not "oct", then throw a DataError. + if (jwk.kty != "oct"_string) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // 3. If jwk does not meet the requirements of Section 6.4 of JSON Web Algorithms [JWA], then throw a DataError. + // Specifically, those requirements are: + // * the member "k" is used to represent a symmetric key (or another key whose value is a single octet sequence). + // * An "alg" member SHOULD also be present to identify the algorithm intended to be used with the key, + // unless the application uses another means or convention to determine the algorithm used. + // NOTE: "k" is already checked in step 4. + if (!jwk.alg.has_value()) + return WebIDL::DataError::create(m_realm, "Missing 'alg' field"_string); + + // 4. Let data be the octet string obtained by decoding the k field of jwk. + data = TRY(parse_jwk_symmetric_key(m_realm, jwk)); + + // 5. -> If data has length 128 bits: + // If the alg field of jwk is present, and is not "A128KW", then throw a DataError. + // -> If data has length 192 bits: + // If the alg field of jwk is present, and is not "A192KW", then throw a DataError. + // -> If data has length 256 bits: + // If the alg field of jwk is present, and is not "A256KW", then throw a DataError. + // -> Otherwise: + // throw a DataError. + auto data_bits = data.size() * 8; + auto const& alg = jwk.alg; + if (data_bits == 128) { + if (alg.has_value() && alg != "A128KW") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); + } else if (data_bits == 192) { + if (alg.has_value() && alg != "A192KW") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); + } else if (data_bits == 256) { + if (alg.has_value() && alg != "A256KW") + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); + } else { + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key size: {} bits", data_bits))); + } + + // 6. If usages is non-empty and the use field of jwk is present and is not "enc", then throw a DataError. + if (!key_usages.is_empty() && jwk.use.has_value() && *jwk.use != "enc"_string) + return WebIDL::DataError::create(m_realm, "Invalid use field"_string); + + // 7. If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK] + // or does not contain all of the specified usages values, then throw a DataError. + TRY(validate_jwk_key_ops(m_realm, jwk, key_usages)); + + // 8. If the ext field of jwk is present and has the value false and extractable is true, then throw a DataError. + if (jwk.ext.has_value() && !*jwk.ext && extractable) + return WebIDL::DataError::create(m_realm, "Invalid ext field"_string); + } + + // 2. Otherwise: + else { + // 1. throw a NotSupportedError. + return WebIDL::NotSupportedError::create(m_realm, "Only raw and jwk formats are supported"_string); + } + + auto data_bits = data.size() * 8; + + // 3. Let key be a new CryptoKey object representing an AES key with value data. + auto key = CryptoKey::create(m_realm, move(data)); + + // 4. Set the [[type]] internal slot of key to "secret". + key->set_type(Bindings::KeyType::Secret); + + // 5. Let algorithm be a new AesKeyAlgorithm. + auto algorithm = AesKeyAlgorithm::create(m_realm); + + // 6. Set the name attribute of algorithm to "AES-KW". + algorithm->set_name("AES-KW"_string); + + // 7. Set the length attribute of algorithm to the length, in bits, of data. + algorithm->set_length(data_bits); + + // 8. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + + // 9. Return key. + return key; +} + +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr> AesKw::export_key(Bindings::KeyFormat format, GC::Ref key) +{ + // 1. If the underlying cryptographic key material represented by the [[handle]] internal slot of key cannot be accessed, then throw an OperationError. + // Note: In our impl this is always accessible + + GC::Ptr result = nullptr; + + // 2. If format is "raw": + if (format == Bindings::KeyFormat::Raw) { + // 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key. + auto data = key->handle().get(); + + // 2. Let result be the result of creating an ArrayBuffer containing data. + result = JS::ArrayBuffer::create(m_realm, data); + } + + // 2. If format is "jwk": + else if (format == Bindings::KeyFormat::Jwk) { + // 1. Let jwk be a new JsonWebKey dictionary. + Bindings::JsonWebKey jwk = {}; + + // 2. Set the kty attribute of jwk to the string "oct". + jwk.kty = "oct"_string; + + // 3. Set the k attribute of jwk to be a string containing the raw octets of the key represented by [[handle]] internal slot of key, + // encoded according to Section 6.4 of JSON Web Algorithms [JWA]. + auto const& key_bytes = key->handle().get(); + jwk.k = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_bytes, AK::OmitPadding::Yes)); + + // 4. -> If the length attribute of key is 128: + // Set the alg attribute of jwk to the string "A128KW". + // -> If the length attribute of key is 192: + // Set the alg attribute of jwk to the string "A192KW". + // -> If the length attribute of key is 256: + // Set the alg attribute of jwk to the string "A256KW". + auto key_bits = key_bytes.size() * 8; + if (key_bits == 128) { + jwk.alg = "A128KW"_string; + } else if (key_bits == 192) { + jwk.alg = "A192KW"_string; + } else if (key_bits == 256) { + jwk.alg = "A256KW"_string; + } + + // 5. Set the key_ops attribute of jwk to the usages attribute of key. + jwk.key_ops = Vector {}; + jwk.key_ops->ensure_capacity(key->internal_usages().size()); + for (auto const& usage : key->internal_usages()) { + jwk.key_ops->append(Bindings::idl_enum_to_string(usage)); + } + + // 6. Set the ext attribute of jwk to equal the [[extractable]] internal slot of key. + jwk.ext = key->extractable(); + + // 7. Let result be the result of converting jwk to an ECMAScript Object, as defined by [WebIDL]. + result = TRY(jwk.to_object(m_realm)); + } + + // 2. Otherwise: + else { + // 1. throw a NotSupportedError. + return WebIDL::NotSupportedError::create(m_realm, "Cannot export to unsupported format"_string); + } + + // 3. Return result. + return GC::Ref { *result }; +} + +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr AesKw::get_key_length(AlgorithmParams const& params) +{ + // 1. If the length member of normalizedDerivedKeyAlgorithm is not 128, 192 or 256, then throw an OperationError. + auto const& normalized_algorithm = static_cast(params); + auto length = normalized_algorithm.length; + if (length != 128 && length != 192 && length != 256) + return WebIDL::OperationError::create(m_realm, "Invalid key length"_string); + + // 2. Return the length member of normalizedDerivedKeyAlgorithm. + return JS::Value(length); +} + +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr, GC::Ref>> AesKw::generate_key(AlgorithmParams const& params, bool extractable, Vector const& key_usages) +{ + // 1. If usages contains any entry which is not one of "wrapKey" or "unwrapKey", then throw a SyntaxError. + for (auto const& usage : key_usages) { + if (usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + // 2. If the length property of normalizedAlgorithm is not equal to one of 128, 192 or 256, then throw an OperationError. + auto const& normalized_algorithm = static_cast(params); + auto const bits = normalized_algorithm.length; + if (bits != 128 && bits != 192 && bits != 256) { + return WebIDL::OperationError::create(m_realm, MUST(String::formatted("Cannot create AES-KW key with unusual amount of {} bits", bits))); + } + + // 3. Generate an AES key of length equal to the length member of normalizedAlgorithm. + // 4. If the key generation step fails, then throw an OperationError. + auto key_buffer = TRY(generate_random_key(m_realm->vm(), bits)); + + // 5. Let key be a new CryptoKey object representing the generated AES key. + auto key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_buffer }); + + // 6. Let algorithm be a new AesKeyAlgorithm. + auto algorithm = AesKeyAlgorithm::create(m_realm); + + // 7. Set the name attribute of algorithm to "AES-KW". + algorithm->set_name("AES-KW"_string); + + // 8. Set the length attribute of algorithm to equal the length member of normalizedAlgorithm. + algorithm->set_length(bits); + + // 9. Set the [[type]] internal slot of key to "secret". + key->set_type(Bindings::KeyType::Secret); + + // 10. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + + // 11. Set the [[extractable]] internal slot of key to be extractable. + key->set_extractable(extractable); + + // 12. Set the [[usages]] internal slot of key to be usages. + key->set_usages(key_usages); + + // 13. Return key. + return { key }; +} + +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr> AesKw::wrap_key(AlgorithmParams const&, GC::Ref key, ByteBuffer const& plaintext) +{ + // 1. If plaintext is not a multiple of 64 bits in length, then throw an OperationError. + if (plaintext.size() % 8 != 0) + return WebIDL::OperationError::create(m_realm, "Invalid plaintext length"_string); + + // 2. Let ciphertext be the result of performing the Key Wrap operation described in Section 2.2.1 of [RFC3394] + // with plaintext as the plaintext to be wrapped and using the default Initial Value defined in Section 2.2.3.1 of the same document. + ::Crypto::Cipher::AESCipher::KWMode cipher { + key->handle().get(), + key->handle().get().size() * 8, + ::Crypto::Cipher::Intent::Encryption, + ::Crypto::Cipher::PaddingMode::Null, + }; + + auto ciphertext = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_uninitialized(plaintext.size() + 8)); + auto ciphertext_bytes = ciphertext.bytes(); + cipher.wrap(plaintext.bytes(), ciphertext_bytes); + + // 3. Return ciphertext. + return JS::ArrayBuffer::create(m_realm, ciphertext); +} + +// https://w3c.github.io/webcrypto/#aes-kw-registration +WebIDL::ExceptionOr> AesKw::unwrap_key(AlgorithmParams const&, GC::Ref key, ByteBuffer const& ciphertext) +{ + // NOTE: The spec does not mention this, but we need to check + if (ciphertext.size() < 8) + return WebIDL::OperationError::create(m_realm, "Invalid ciphertext length"_string); + + // 1. Let plaintext be the result of performing the Key Unwrap operation described in Section 2.2.2 of [RFC3394] + // with ciphertext as the input ciphertext and using the default Initial Value defined in Section 2.2.3.1 of the same document + ::Crypto::Cipher::AESCipher::KWMode cipher { + key->handle().get(), + key->handle().get().size() * 8, + ::Crypto::Cipher::Intent::Decryption, + ::Crypto::Cipher::PaddingMode::Null, + }; + + // 2. If the Key Unwrap operation returns an error, then throw an OperationError. + auto out = TRY_OR_THROW_OOM(m_realm->vm(), ByteBuffer::create_uninitialized(ciphertext.size() - 8)); + auto out_bytes = out.bytes(); + if (cipher.unwrap(ciphertext, out_bytes) != ::Crypto::VerificationConsistency::Consistent) + return WebIDL::OperationError::create(m_realm, "Key unwrap failed"_string); + + // 3. Return plaintext. + return JS::ArrayBuffer::create(m_realm, out); +} + // https://w3c.github.io/webcrypto/#hkdf-operations WebIDL::ExceptionOr> HKDF::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& key_usages) { diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 9dd1954c78424..2acef1f3e0579 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -431,6 +431,24 @@ class AesGcm : public AlgorithmMethods { } }; +class AesKw : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; + virtual WebIDL::ExceptionOr> export_key(Bindings::KeyFormat, GC::Ref) override; + virtual WebIDL::ExceptionOr get_key_length(AlgorithmParams const&) override; + virtual WebIDL::ExceptionOr, GC::Ref>> generate_key(AlgorithmParams const&, bool, Vector const&) override; + virtual WebIDL::ExceptionOr> wrap_key(AlgorithmParams const&, GC::Ref, ByteBuffer const&) override; + virtual WebIDL::ExceptionOr> unwrap_key(AlgorithmParams const&, GC::Ref, ByteBuffer const&) override; + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new AesKw(realm)); } + +private: + explicit AesKw(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + class HKDF : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index cf283ec500d69..25fd18a8556a4 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -1130,12 +1130,12 @@ SupportedAlgorithmsMap const& supported_algorithms() define_an_algorithm("get key length"_string, "AES-GCM"_string); // https://w3c.github.io/webcrypto/#aes-kw-registration - // FIXME: define_an_algorithm("wrapKey"_string, "AES-KW"_string); - // FIXME: define_an_algorithm("unwrapKey"_string, "AES-KW"_string); - // FIXME: define_an_algorithm("generateKey"_string, "AES-KW"_string); - // FIXME: define_an_algorithm("importKey"_string, "AES-KW"_string); - // FIXME: define_an_algorithm("exportKey"_string, "AES-KW"_string); - // FIXME: define_an_algorithm("get key length"_string, "AES-KW"_string); + define_an_algorithm("wrapKey"_string, "AES-KW"_string); + define_an_algorithm("unwrapKey"_string, "AES-KW"_string); + define_an_algorithm("generateKey"_string, "AES-KW"_string); + define_an_algorithm("importKey"_string, "AES-KW"_string); + define_an_algorithm("exportKey"_string, "AES-KW"_string); + define_an_algorithm("get key length"_string, "AES-KW"_string); // https://w3c.github.io/webcrypto/#hmac-registration define_an_algorithm("sign"_string, "HMAC"_string); diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.txt new file mode 100644 index 0000000000000..7a46058a2568f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.txt @@ -0,0 +1,522 @@ +Harness status: OK + +Found 516 tests + +488 Pass +28 Fail +Pass Bad algorithm: generateKey(AES, false, [decrypt]) +Pass Bad algorithm: generateKey(AES, true, [decrypt]) +Pass Bad algorithm: generateKey(AES, RED, [decrypt]) +Pass Bad algorithm: generateKey(AES, 7, [decrypt]) +Pass Bad algorithm: generateKey(AES, false, [sign, decrypt]) +Pass Bad algorithm: generateKey(AES, true, [sign, decrypt]) +Pass Bad algorithm: generateKey(AES, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey(AES, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey(AES, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey(AES, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey(AES, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey(AES, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey(AES, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey(AES, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey(AES, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey(AES, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey(AES, false, [sign]) +Pass Bad algorithm: generateKey(AES, true, [sign]) +Pass Bad algorithm: generateKey(AES, RED, [sign]) +Pass Bad algorithm: generateKey(AES, 7, [sign]) +Pass Bad algorithm: generateKey(AES, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey(AES, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey(AES, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey(AES, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey(AES, false, [deriveBits]) +Pass Bad algorithm: generateKey(AES, true, [deriveBits]) +Pass Bad algorithm: generateKey(AES, RED, [deriveBits]) +Pass Bad algorithm: generateKey(AES, 7, [deriveBits]) +Pass Bad algorithm: generateKey(AES, false, []) +Pass Bad algorithm: generateKey(AES, true, []) +Pass Bad algorithm: generateKey(AES, RED, []) +Pass Bad algorithm: generateKey(AES, 7, []) +Pass Bad algorithm: generateKey(AES, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey(AES, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey(AES, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey(AES, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, false, [decrypt]) +Pass Bad algorithm: generateKey({name: AES}, true, [decrypt]) +Pass Bad algorithm: generateKey({name: AES}, RED, [decrypt]) +Pass Bad algorithm: generateKey({name: AES}, 7, [decrypt]) +Pass Bad algorithm: generateKey({name: AES}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: AES}, false, [sign]) +Pass Bad algorithm: generateKey({name: AES}, true, [sign]) +Pass Bad algorithm: generateKey({name: AES}, RED, [sign]) +Pass Bad algorithm: generateKey({name: AES}, 7, [sign]) +Pass Bad algorithm: generateKey({name: AES}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: AES}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: AES}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: AES}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: AES}, false, [deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, true, [deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, false, []) +Pass Bad algorithm: generateKey({name: AES}, true, []) +Pass Bad algorithm: generateKey({name: AES}, RED, []) +Pass Bad algorithm: generateKey({name: AES}, 7, []) +Pass Bad algorithm: generateKey({name: AES}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: AES}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, []) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, []) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, []) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, []) +Pass Bad algorithm: generateKey({length: 128, name: AES}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CMAC}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, []) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({length: 128, name: AES-CFB}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [deriveBits, sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [deriveBits, sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [deriveBits, sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [deriveBits, sign, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [deriveBits, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [deriveBits, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [deriveBits, decrypt]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [sign]) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [sign]) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [sign]) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [sign]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [deriveBits, sign]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [deriveBits, sign]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [deriveBits, sign]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [deriveBits, sign]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, []) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, []) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, []) +Pass Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, []) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Fail Bad algorithm: generateKey({hash: MD5, name: HMAC}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, []) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, []) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, []) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, []) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA-256, modulusLength: 2048, name: RSA, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, []) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, []) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, []) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, []) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({hash: SHA, modulusLength: 2048, name: RSA-PSS, publicExponent: {0: 1, 1: 0, 2: 1}}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [deriveBits, sign, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [deriveBits, decrypt]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [deriveBits, sign]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, []) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, []) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, []) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, []) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad algorithm: generateKey({name: EC, namedCurve: P521}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Empty algorithm: generateKey({}, false, [decrypt]) +Pass Empty algorithm: generateKey({}, true, [decrypt]) +Pass Empty algorithm: generateKey({}, RED, [decrypt]) +Pass Empty algorithm: generateKey({}, 7, [decrypt]) +Pass Empty algorithm: generateKey({}, false, [sign, decrypt]) +Pass Empty algorithm: generateKey({}, true, [sign, decrypt]) +Pass Empty algorithm: generateKey({}, RED, [sign, decrypt]) +Pass Empty algorithm: generateKey({}, 7, [sign, decrypt]) +Pass Empty algorithm: generateKey({}, false, [deriveBits, sign, decrypt]) +Pass Empty algorithm: generateKey({}, true, [deriveBits, sign, decrypt]) +Pass Empty algorithm: generateKey({}, RED, [deriveBits, sign, decrypt]) +Pass Empty algorithm: generateKey({}, 7, [deriveBits, sign, decrypt]) +Pass Empty algorithm: generateKey({}, false, [deriveBits, decrypt]) +Pass Empty algorithm: generateKey({}, true, [deriveBits, decrypt]) +Pass Empty algorithm: generateKey({}, RED, [deriveBits, decrypt]) +Pass Empty algorithm: generateKey({}, 7, [deriveBits, decrypt]) +Pass Empty algorithm: generateKey({}, false, [sign]) +Pass Empty algorithm: generateKey({}, true, [sign]) +Pass Empty algorithm: generateKey({}, RED, [sign]) +Pass Empty algorithm: generateKey({}, 7, [sign]) +Pass Empty algorithm: generateKey({}, false, [deriveBits, sign]) +Pass Empty algorithm: generateKey({}, true, [deriveBits, sign]) +Pass Empty algorithm: generateKey({}, RED, [deriveBits, sign]) +Pass Empty algorithm: generateKey({}, 7, [deriveBits, sign]) +Pass Empty algorithm: generateKey({}, false, [deriveBits]) +Pass Empty algorithm: generateKey({}, true, [deriveBits]) +Pass Empty algorithm: generateKey({}, RED, [deriveBits]) +Pass Empty algorithm: generateKey({}, 7, [deriveBits]) +Pass Empty algorithm: generateKey({}, false, []) +Pass Empty algorithm: generateKey({}, true, []) +Pass Empty algorithm: generateKey({}, RED, []) +Pass Empty algorithm: generateKey({}, 7, []) +Pass Empty algorithm: generateKey({}, false, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Empty algorithm: generateKey({}, true, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Empty algorithm: generateKey({}, RED, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Empty algorithm: generateKey({}, 7, [decrypt, sign, deriveBits, decrypt, sign, deriveBits]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [encrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [decrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [sign]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, sign]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, sign]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, sign]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, sign]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [verify]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, verify]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, verify]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, verify]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, verify]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [deriveKey]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [deriveBits]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [encrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [decrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [sign]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, sign]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, sign]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, sign]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, sign]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [verify]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, verify]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, verify]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, verify]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, verify]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [deriveKey]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [deriveBits]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [encrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, encrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, encrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [decrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, decrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, decrypt]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [sign]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, sign]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, sign]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, sign]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, sign]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [verify]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, verify]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, verify]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, verify]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, verify]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [deriveKey]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveKey]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [deriveBits]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, deriveBits]) +Pass Bad usages: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey, deriveBits]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 64, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 127, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 129, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 255, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 257, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, false, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, true, [wrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, false, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, true, [unwrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, false, []) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, true, []) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Bad algorithm property: generateKey({length: 512, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty usages: generateKey({length: 128, name: AES-KW}, false, []) +Pass Empty usages: generateKey({length: 128, name: AES-KW}, true, []) +Pass Empty usages: generateKey({length: 192, name: AES-KW}, false, []) +Pass Empty usages: generateKey({length: 192, name: AES-KW}, true, []) +Pass Empty usages: generateKey({length: 256, name: AES-KW}, false, []) +Pass Empty usages: generateKey({length: 256, name: AES-KW}, true, []) \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.txt new file mode 100644 index 0000000000000..553eadc7e90e1 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.txt @@ -0,0 +1,77 @@ +Harness status: OK + +Found 72 tests + +72 Pass +Pass Success: generateKey({length: 128, name: AES-KW}, false, [wrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, true, [wrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, false, [unwrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, true, [unwrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 128, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, false, [wrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, true, [wrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, false, [unwrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, true, [unwrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, false, [wrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, true, [wrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, false, [unwrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, true, [unwrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 128, name: aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 128, name: Aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 192, name: Aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, false, [wrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, true, [wrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, false, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, true, [unwrapKey, wrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, false, [unwrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, true, [unwrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Success: generateKey({length: 256, name: Aes-kw}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.txt new file mode 100644 index 0000000000000..268b6090eea3f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.txt @@ -0,0 +1,395 @@ +Harness status: OK + +Found 390 tests + +390 Pass +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, []) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, true, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128CTR, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CTR}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, []) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, true, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CTR}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, true, []) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, true, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, true, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, true, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CTR}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, false, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, false, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256CTR, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CTR}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, []) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, true, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128CBC, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-CBC}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, []) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, true, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-CBC}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, true, []) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, true, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, true, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, true, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-CBC}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, false, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, false, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256CBC, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-CBC}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, []) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, true, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 128 bits (jwk, {alg: A128GCM, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-GCM}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, []) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, true, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 192 bits (jwk, {alg: A192GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-GCM}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, true, []) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, true, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, true, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, true, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, true, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, true, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-GCM}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, false, [encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, false, [decrypt, encrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, false, [decrypt]) +Pass Good parameters: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, false, [encrypt, decrypt, encrypt, decrypt]) +Pass Empty Usages: 256 bits (jwk, {alg: A256GCM, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-GCM}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, []) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, true, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 128 bits (jwk, {alg: A128KW, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {name: AES-KW}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, []) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, true, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 192 bits (jwk, {alg: A192KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {name: AES-KW}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, true, []) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, true, [wrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, true, [unwrapKey, wrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, true, [unwrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, true, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, true, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: AES-KW}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, false, [wrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, false, [unwrapKey, wrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, false, [unwrapKey]) +Pass Good parameters: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, false, [wrapKey, unwrapKey, wrapKey, unwrapKey]) +Pass Empty Usages: 256 bits (jwk, {alg: A256KW, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {name: AES-KW}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-1, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (jwk, {alg: HS1, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-1, name: HMAC}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-256, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (jwk, {alg: HS256, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-256, name: HMAC}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-384, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (jwk, {alg: HS384, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-384, name: HMAC}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 128 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 128 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 128 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 128 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEA, kty: oct}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 192 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 192 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 192 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 192 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcY, kty: oct}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 256 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify, sign]) +Pass Good parameters: 256 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [verify]) +Pass Good parameters: 256 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-512, name: HMAC}, false, [sign, verify, sign, verify]) +Pass Empty Usages: 256 bits (jwk, {alg: HS512, k: AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA, kty: oct}, {hash: SHA-512, name: HMAC}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey, deriveBits]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey, deriveBits]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey, deriveBits]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: HKDF}, false, []) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey, deriveBits]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey]) +Pass Good parameters: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 128 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, []) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey, deriveBits]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey]) +Pass Good parameters: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 192 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, []) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey, deriveBits]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveKey]) +Pass Good parameters: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, [deriveBits, deriveKey, deriveBits, deriveKey]) +Pass Empty Usages: 256 bits (raw, {0: 1, 1: 2, 10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20, 2: 3, 20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27, 27: 28, 28: 29, 29: 30, 3: 4, 30: 31, 31: 32, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}, {name: PBKDF2}, false, []) \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt index 23d05812f6bde..56e56444c317c 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 244 tests +Found 281 tests -244 Pass +281 Pass Pass setup Pass Can wrap and unwrap RSA-OAEP public key keys using spki and RSA-OAEP Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and RSA-OAEP @@ -56,6 +56,11 @@ Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP Pass Can wrap and unwrap AES-GCM keys using jwk and RSA-OAEP Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP Pass Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-KW keys using raw and RSA-OAEP +Pass Can wrap and unwrap AES-KW keys as non-extractable using raw and RSA-OAEP +Pass Can wrap and unwrap AES-KW keys using jwk and RSA-OAEP +Pass Can wrap and unwrap AES-KW keys as non-extractable using jwk and RSA-OAEP +Pass Can unwrap AES-KW non-extractable keys using jwk and RSA-OAEP Pass Can wrap and unwrap HMAC keys using raw and RSA-OAEP Pass Can wrap and unwrap HMAC keys as non-extractable using raw and RSA-OAEP Pass Can wrap and unwrap HMAC keys using jwk and RSA-OAEP @@ -118,6 +123,11 @@ Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CTR Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-KW keys using raw and AES-CTR +Pass Can wrap and unwrap AES-KW keys as non-extractable using raw and AES-CTR +Pass Can wrap and unwrap AES-KW keys using jwk and AES-CTR +Pass Can wrap and unwrap AES-KW keys as non-extractable using jwk and AES-CTR +Pass Can unwrap AES-KW non-extractable keys using jwk and AES-CTR Pass Can wrap and unwrap HMAC keys using raw and AES-CTR Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CTR Pass Can wrap and unwrap HMAC keys using jwk and AES-CTR @@ -180,6 +190,11 @@ Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CBC Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-KW keys using raw and AES-CBC +Pass Can wrap and unwrap AES-KW keys as non-extractable using raw and AES-CBC +Pass Can wrap and unwrap AES-KW keys using jwk and AES-CBC +Pass Can wrap and unwrap AES-KW keys as non-extractable using jwk and AES-CBC +Pass Can unwrap AES-KW non-extractable keys using jwk and AES-CBC Pass Can wrap and unwrap HMAC keys using raw and AES-CBC Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CBC Pass Can wrap and unwrap HMAC keys using jwk and AES-CBC @@ -242,8 +257,30 @@ Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-GCM Pass Can wrap and unwrap AES-GCM keys using jwk and AES-GCM Pass Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-GCM Pass Can unwrap AES-GCM non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-KW keys using raw and AES-GCM +Pass Can wrap and unwrap AES-KW keys as non-extractable using raw and AES-GCM +Pass Can wrap and unwrap AES-KW keys using jwk and AES-GCM +Pass Can wrap and unwrap AES-KW keys as non-extractable using jwk and AES-GCM +Pass Can unwrap AES-KW non-extractable keys using jwk and AES-GCM Pass Can wrap and unwrap HMAC keys using raw and AES-GCM Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM Pass Can wrap and unwrap HMAC keys using jwk and AES-GCM Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM -Pass Can unwrap HMAC non-extractable keys using jwk and AES-GCM \ No newline at end of file +Pass Can unwrap HMAC non-extractable keys using jwk and AES-GCM +Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-KW +Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-KW +Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-KW +Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-KW +Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-KW +Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-KW +Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-KW +Pass Can wrap and unwrap AES-CTR keys using raw and AES-KW +Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-KW +Pass Can wrap and unwrap AES-CBC keys using raw and AES-KW +Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-KW +Pass Can wrap and unwrap AES-GCM keys using raw and AES-KW +Pass Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-KW +Pass Can wrap and unwrap AES-KW keys using raw and AES-KW +Pass Can wrap and unwrap AES-KW keys as non-extractable using raw and AES-KW +Pass Can wrap and unwrap HMAC keys using raw and AES-KW +Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-KW \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.html new file mode 100644 index 0000000000000..72af5ede0e48f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.html @@ -0,0 +1,17 @@ + + +WebCryptoAPI: generateKey() for Failures + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js new file mode 100644 index 0000000000000..40c199b29a5c6 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/failures_AES-KW.https.any.js @@ -0,0 +1,5 @@ +// META: title=WebCryptoAPI: generateKey() for Failures +// META: timeout=long +// META: script=../util/helpers.js +// META: script=failures.js +run_test(["AES-KW"]); diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.html new file mode 100644 index 0000000000000..19ff9ff061f9a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.html @@ -0,0 +1,18 @@ + + +WebCryptoAPI: generateKey() Successful Calls + + + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js new file mode 100644 index 0000000000000..dbc040fdc5cb4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/generateKey/successes_AES-KW.https.any.js @@ -0,0 +1,6 @@ +// META: title=WebCryptoAPI: generateKey() Successful Calls +// META: timeout=long +// META: script=../util/helpers.js +// META: script=/common/subset-tests.js +// META: script=successes.js +run_test(["AES-KW"]); diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.html b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.html new file mode 100644 index 0000000000000..c57ad32fe6302 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.html @@ -0,0 +1,16 @@ + + +WebCryptoAPI: importKey() for symmetric keys + + + + + +
    + diff --git a/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.js b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.js new file mode 100644 index 0000000000000..01b3180189db8 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/WebCryptoAPI/import_export/symmetric_importKey.https.any.js @@ -0,0 +1,222 @@ +// META: title=WebCryptoAPI: importKey() for symmetric keys +// META: timeout=long +// META: script=../util/helpers.js + +// Test importKey and exportKey for non-PKC algorithms. Only "happy paths" are +// currently tested - those where the operation should succeed. + + var subtle = crypto.subtle; + + // keying material for algorithms that can use any bit string. + var rawKeyData = [ + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24]), + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]) + ]; + + // combinations of algorithms, usages, parameters, and formats to test + var testVectors = [ + {name: "AES-CTR", legalUsages: ["encrypt", "decrypt"], extractable: [true, false], formats: ["raw", "jwk"]}, + {name: "AES-CBC", legalUsages: ["encrypt", "decrypt"], extractable: [true, false], formats: ["raw", "jwk"]}, + {name: "AES-GCM", legalUsages: ["encrypt", "decrypt"], extractable: [true, false], formats: ["raw", "jwk"]}, + {name: "AES-KW", legalUsages: ["wrapKey", "unwrapKey"], extractable: [true, false], formats: ["raw", "jwk"]}, + {name: "HMAC", hash: "SHA-1", legalUsages: ["sign", "verify"], extractable: [false], formats: ["raw", "jwk"]}, + {name: "HMAC", hash: "SHA-256", legalUsages: ["sign", "verify"], extractable: [false], formats: ["raw", "jwk"]}, + {name: "HMAC", hash: "SHA-384", legalUsages: ["sign", "verify"], extractable: [false], formats: ["raw", "jwk"]}, + {name: "HMAC", hash: "SHA-512", legalUsages: ["sign", "verify"], extractable: [false], formats: ["raw", "jwk"]}, + {name: "HKDF", legalUsages: ["deriveBits", "deriveKey"], extractable: [false], formats: ["raw"]}, + {name: "PBKDF2", legalUsages: ["deriveBits", "deriveKey"], extractable: [false], formats: ["raw"]} + ]; + + + + // TESTS ARE HERE: + // Test every test vector, along with all available key data + testVectors.forEach(function(vector) { + var algorithm = {name: vector.name}; + if ("hash" in vector) { + algorithm.hash = vector.hash; + } + + rawKeyData.forEach(function(keyData) { + // Try each legal value of the extractable parameter + vector.extractable.forEach(function(extractable) { + vector.formats.forEach(function(format) { + var data = keyData; + if (format === "jwk") { + data = jwkData(keyData, algorithm); + } + // Generate all combinations of valid usages for testing + allValidUsages(vector.legalUsages).forEach(function(usages) { + testFormat(format, algorithm, data, keyData.length * 8, usages, extractable); + }); + testEmptyUsages(format, algorithm, data, keyData.length * 8, extractable); + }); + }); + + }); + }); + + function hasLength(algorithm) { + return algorithm.name === 'HMAC' || algorithm.name.startsWith('AES'); + } + + // Test importKey with a given key format and other parameters. If + // extrable is true, export the key and verify that it matches the input. + function testFormat(format, algorithm, keyData, keySize, usages, extractable) { + promise_test(function(test) { + return subtle.importKey(format, keyData, algorithm, extractable, usages). + then(function(key) { + assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, hasLength(key.algorithm) ? { length: keySize, ...algorithm } : algorithm, extractable, usages, 'secret'); + if (!extractable) { + return; + } + + return subtle.exportKey(format, key). + then(function(result) { + if (format !== "jwk") { + assert_true(equalBuffers(keyData, result), "Round trip works"); + } else { + assert_true(equalJwk(keyData, result), "Round trip works"); + } + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, function(err) { + assert_unreached("Threw an unexpected error: " + err.toString()); + }); + }, "Good parameters: " + keySize.toString() + " bits " + parameterString(format, keyData, algorithm, extractable, usages)); + } + + // Test importKey with a given key format and other parameters but with empty usages. + // Should fail with SyntaxError + function testEmptyUsages(format, algorithm, keyData, keySize, extractable) { + const usages = []; + promise_test(function(test) { + return subtle.importKey(format, keyData, algorithm, extractable, usages). + then(function(key) { + assert_unreached("importKey succeeded but should have failed with SyntaxError"); + }, function(err) { + assert_equals(err.name, "SyntaxError", "Should throw correct error, not " + err.name + ": " + err.message); + }); + }, "Empty Usages: " + keySize.toString() + " bits " + parameterString(format, keyData, algorithm, extractable, usages)); + } + + + + // Helper methods follow: + + // Are two array buffers the same? + function equalBuffers(a, b) { + if (a.byteLength !== b.byteLength) { + return false; + } + + var aBytes = new Uint8Array(a); + var bBytes = new Uint8Array(b); + + for (var i=0; i Date: Sun, 15 Dec 2024 05:39:12 +0500 Subject: [PATCH 217/237] LibCore: Add System::getpid --- Libraries/LibCore/System.cpp | 5 +++++ Libraries/LibCore/System.h | 2 ++ Libraries/LibCore/SystemWindows.cpp | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/Libraries/LibCore/System.cpp b/Libraries/LibCore/System.cpp index de3aab106670c..500e8eaec0827 100644 --- a/Libraries/LibCore/System.cpp +++ b/Libraries/LibCore/System.cpp @@ -999,4 +999,9 @@ ErrorOr set_resource_limits(int resource, rlim_t limit) return {}; } +int getpid() +{ + return ::getpid(); +} + } diff --git a/Libraries/LibCore/System.h b/Libraries/LibCore/System.h index 0a6edcbff1518..850e9324e8b32 100644 --- a/Libraries/LibCore/System.h +++ b/Libraries/LibCore/System.h @@ -179,4 +179,6 @@ ErrorOr get_resource_limits(int resource); ErrorOr set_resource_limits(int resource, rlim_t limit); #endif +int getpid(); + } diff --git a/Libraries/LibCore/SystemWindows.cpp b/Libraries/LibCore/SystemWindows.cpp index a9f6165192ff8..8fe6f69021f66 100644 --- a/Libraries/LibCore/SystemWindows.cpp +++ b/Libraries/LibCore/SystemWindows.cpp @@ -184,4 +184,9 @@ ErrorOr munmap(void* address, size_t size) return {}; } +int getpid() +{ + return GetCurrentProcessId(); +} + } From 969fb1a3a8e4b0e6447824172016e321a413412c Mon Sep 17 00:00:00 2001 From: stasoid Date: Sun, 15 Dec 2024 05:40:31 +0500 Subject: [PATCH 218/237] LibRequests: Port to Windows --- Libraries/LibRequests/CMakeLists.txt | 5 +++++ Libraries/LibRequests/RequestClient.cpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Libraries/LibRequests/CMakeLists.txt b/Libraries/LibRequests/CMakeLists.txt index 65f71ca42309e..835b0c233c8ff 100644 --- a/Libraries/LibRequests/CMakeLists.txt +++ b/Libraries/LibRequests/CMakeLists.txt @@ -12,3 +12,8 @@ set(GENERATED_SOURCES serenity_lib(LibRequests requests) target_link_libraries(LibRequests PRIVATE LibCore LibIPC) + +if (WIN32) + find_package(pthread REQUIRED) + target_include_directories(LibRequests PRIVATE ${PTHREAD_INCLUDE_DIR}) +endif() diff --git a/Libraries/LibRequests/RequestClient.cpp b/Libraries/LibRequests/RequestClient.cpp index 7df8af35d317b..be6d43d331385 100644 --- a/Libraries/LibRequests/RequestClient.cpp +++ b/Libraries/LibRequests/RequestClient.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include @@ -19,7 +20,7 @@ RequestClient::~RequestClient() = default; void RequestClient::die() { // FIXME: Gracefully handle this, or relaunch and reconnect to RequestServer. - warnln("\033[31;1m {} Lost connection to RequestServer\033[0m", getpid()); + warnln("\033[31;1m {} Lost connection to RequestServer\033[0m", Core::System::getpid()); } void RequestClient::ensure_connection(URL::URL const& url, ::RequestServer::CacheLevel cache_level) From 27a654c739303cf7a9014e2bf148f9334244e20e Mon Sep 17 00:00:00 2001 From: stasoid Date: Sat, 14 Dec 2024 15:10:10 +0500 Subject: [PATCH 219/237] LibGC: Port to Windows --- Libraries/LibGC/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/LibGC/CMakeLists.txt b/Libraries/LibGC/CMakeLists.txt index 7d93203ab7037..805c7461146a0 100644 --- a/Libraries/LibGC/CMakeLists.txt +++ b/Libraries/LibGC/CMakeLists.txt @@ -22,3 +22,9 @@ if (ENABLE_SWIFT) target_link_libraries(LibGC PRIVATE AK) add_swift_target_properties(LibGC LAGOM_LIBRARIES AK) endif() + +if (WIN32) + find_package(mman REQUIRED) + target_include_directories(LibGC PRIVATE ${MMAN_INCLUDE_DIR}) + target_link_libraries(LibGC PRIVATE ${MMAN_LIBRARY}) +endif() From 4f691c2410f2fdf972ce63d4ad257e0a05cdfb46 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 21:40:25 +0000 Subject: [PATCH 220/237] LibWeb/WebAudio: Implement PannerNode Required by https://scottts.itch.io/different-strokes --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/WebAudio/PannerNode.cpp | 166 ++++++++++++++++++ Libraries/LibWeb/WebAudio/PannerNode.h | 130 ++++++++++++++ Libraries/LibWeb/WebAudio/PannerNode.idl | 56 ++++++ Libraries/LibWeb/idl_files.cmake | 1 + .../Text/expected/all-window-properties.txt | 1 + 7 files changed, 356 insertions(+) create mode 100644 Libraries/LibWeb/WebAudio/PannerNode.cpp create mode 100644 Libraries/LibWeb/WebAudio/PannerNode.h create mode 100644 Libraries/LibWeb/WebAudio/PannerNode.idl diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 598d6457a0fc8..e1ae31aa53488 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -787,6 +787,7 @@ set(SOURCES WebAudio/GainNode.cpp WebAudio/OfflineAudioContext.cpp WebAudio/OscillatorNode.cpp + WebAudio/PannerNode.cpp WebAudio/PeriodicWave.cpp WebDriver/Actions.cpp WebDriver/Capabilities.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 98f27d2e5a81d..1dbc892ad516a 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -822,6 +822,7 @@ class DynamicsCompressorNode; class GainNode; class OfflineAudioContext; class OscillatorNode; +class PannerNode; class PeriodicWave; enum class AudioContextState; diff --git a/Libraries/LibWeb/WebAudio/PannerNode.cpp b/Libraries/LibWeb/WebAudio/PannerNode.cpp new file mode 100644 index 0000000000000..4caec4852a1f8 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +GC_DEFINE_ALLOCATOR(PannerNode); + +PannerNode::~PannerNode() = default; + +WebIDL::ExceptionOr> PannerNode::create(JS::Realm& realm, GC::Ref context, PannerOptions const& options) +{ + return construct_impl(realm, context, options); +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-pannernode +WebIDL::ExceptionOr> PannerNode::construct_impl(JS::Realm& realm, GC::Ref context, PannerOptions const& options) +{ + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + // A RangeError exception MUST be thrown if this is set to a negative value. + if (options.ref_distance < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "refDistance cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + // A RangeError exception MUST be thrown if this is set to a negative value. + if (options.rolloff_factor < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "rolloffFactor cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + // A RangeError exception MUST be thrown if this is set to a non-positive value. + if (options.max_distance < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "maxDistance cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + // It is a linear value (not dB) in the range [0, 1]. An InvalidStateError MUST be thrown if the parameter is outside this range. + if (options.cone_outer_gain < 0.0 || options.cone_outer_gain > 1.0) + return WebIDL::InvalidStateError::create(realm, "coneOuterGain must be in the range of [0, 1]"_string); + + // Create the node and allocate memory + auto node = realm.create(realm, context, options); + + // Default options for channel count and interpretation + // https://webaudio.github.io/web-audio-api/#PannerNode + AudioNodeDefaultOptions default_options; + default_options.channel_count_mode = Bindings::ChannelCountMode::ClampedMax; + default_options.channel_interpretation = Bindings::ChannelInterpretation::Speakers; + default_options.channel_count = 2; + // FIXME: Set tail-time to maybe + + TRY(node->initialize_audio_node_options(options, default_options)); + return node; +} + +PannerNode::PannerNode(JS::Realm& realm, GC::Ref context, PannerOptions const& options) + : AudioNode(realm, context) + , m_position_x(AudioParam::create(realm, options.position_x, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_position_y(AudioParam::create(realm, options.position_y, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_position_z(AudioParam::create(realm, options.position_z, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_x(AudioParam::create(realm, options.orientation_x, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_y(AudioParam::create(realm, options.orientation_y, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_z(AudioParam::create(realm, options.orientation_z, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_ref_distance(options.ref_distance) + , m_max_distance(options.max_distance) + , m_rolloff_factor(options.rolloff_factor) + , m_cone_inner_angle(options.cone_inner_angle) + , m_cone_outer_angle(options.cone_outer_angle) + , m_cone_outer_gain(options.cone_outer_gain) +{ +} + +void PannerNode::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(PannerNode); +} + +void PannerNode::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_position_x); + visitor.visit(m_position_y); + visitor.visit(m_position_z); + visitor.visit(m_orientation_x); + visitor.visit(m_orientation_y); + visitor.visit(m_orientation_z); +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance +WebIDL::ExceptionOr PannerNode::set_ref_distance(double value) +{ + // A RangeError exception MUST be thrown if this is set to a negative value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "refDistance cannot be negative"sv }; + + m_ref_distance = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance +WebIDL::ExceptionOr PannerNode::set_max_distance(double value) +{ + // A RangeError exception MUST be thrown if this is set to a non-positive value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "maxDistance cannot be negative"sv }; + + m_max_distance = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor +WebIDL::ExceptionOr PannerNode::set_rolloff_factor(double value) +{ + // A RangeError exception MUST be thrown if this is set to a negative value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "rolloffFactor cannot be negative"sv }; + + m_rolloff_factor = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain +WebIDL::ExceptionOr PannerNode::set_cone_outer_gain(double value) +{ + // It is a linear value (not dB) in the range [0, 1]. An InvalidStateError MUST be thrown if the parameter is outside this range. + if (value < 0.0 || value > 1.0) + return WebIDL::InvalidStateError::create(realm(), "coneOuterGain must be in the range of [0, 1]"_string); + + m_cone_outer_gain = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-setposition +WebIDL::ExceptionOr PannerNode::set_position(float x, float y, float z) +{ + // This method is DEPRECATED. It is equivalent to setting positionX.value, positionY.value, and positionZ.value + // attribute directly with the x, y and z parameters, respectively. + // FIXME: Consequently, if any of the positionX, positionY, and positionZ AudioParams have an automation curve + // set using setValueCurveAtTime() at the time this method is called, a NotSupportedError MUST be thrown. + m_position_x->set_value(x); + m_position_y->set_value(y); + m_position_z->set_value(z); + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-setorientation +WebIDL::ExceptionOr PannerNode::set_orientation(float x, float y, float z) +{ + // This method is DEPRECATED. It is equivalent to setting orientationX.value, orientationY.value, and + // orientationZ.value attribute directly, with the x, y and z parameters, respectively. + // FIXME: Consequently, if any of the orientationX, orientationY, and orientationZ AudioParams have an automation + // curve set using setValueCurveAtTime() at the time this method is called, a NotSupportedError MUST be thrown. + m_orientation_x->set_value(x); + m_orientation_y->set_value(y); + m_orientation_z->set_value(z); + return {}; +} + +} diff --git a/Libraries/LibWeb/WebAudio/PannerNode.h b/Libraries/LibWeb/WebAudio/PannerNode.h new file mode 100644 index 0000000000000..4be601d61689a --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::WebAudio { + +// https://webaudio.github.io/web-audio-api/#PannerOptions +struct PannerOptions : AudioNodeOptions { + Bindings::PanningModelType panning_model { Bindings::PanningModelType::Equalpower }; + Bindings::DistanceModelType distance_model { Bindings::DistanceModelType::Inverse }; + float position_x { 0.0f }; + float position_y { 0.0f }; + float position_z { 0.0f }; + float orientation_x { 1.0f }; + float orientation_y { 0.0f }; + float orientation_z { 0.0f }; + double ref_distance { 1.0 }; + double max_distance { 10000.0 }; + double rolloff_factor { 1.0 }; + double cone_inner_angle { 360.0 }; + double cone_outer_angle { 360.0 }; + double cone_outer_gain { 0.0 }; +}; + +// https://webaudio.github.io/web-audio-api/#PannerNode +class PannerNode final : public AudioNode { + WEB_PLATFORM_OBJECT(PannerNode, AudioNode); + GC_DECLARE_ALLOCATOR(PannerNode); + +public: + virtual ~PannerNode() override; + + static WebIDL::ExceptionOr> create(JS::Realm&, GC::Ref, PannerOptions const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref, PannerOptions const& = {}); + + WebIDL::UnsignedLong number_of_inputs() override { return 1; } + WebIDL::UnsignedLong number_of_outputs() override { return 1; } + + GC::Ref position_x() const { return m_position_x; } + GC::Ref position_y() const { return m_position_y; } + GC::Ref position_z() const { return m_position_z; } + GC::Ref orientation_x() const { return m_orientation_x; } + GC::Ref orientation_y() const { return m_orientation_y; } + GC::Ref orientation_z() const { return m_orientation_z; } + + Bindings::PanningModelType panning_model() const { return m_panning_model; } + void set_panning_model(Bindings::PanningModelType value) { m_panning_model = value; } + + Bindings::DistanceModelType distance_model() const { return m_distance_model; } + void set_distance_model(Bindings::DistanceModelType value) { m_distance_model = value; } + + double ref_distance() const { return m_ref_distance; } + WebIDL::ExceptionOr set_ref_distance(double); + + double max_distance() const { return m_max_distance; } + WebIDL::ExceptionOr set_max_distance(double); + + double rolloff_factor() const { return m_rolloff_factor; } + WebIDL::ExceptionOr set_rolloff_factor(double); + + double cone_inner_angle() const { return m_cone_inner_angle; } + void set_cone_inner_angle(double value) { m_cone_inner_angle = value; } + + double cone_outer_angle() const { return m_cone_outer_angle; } + void set_cone_outer_angle(double value) { m_cone_outer_angle = value; } + + double cone_outer_gain() const { return m_cone_outer_gain; } + WebIDL::ExceptionOr set_cone_outer_gain(double); + + WebIDL::ExceptionOr set_position(float x, float y, float z); + WebIDL::ExceptionOr set_orientation(float x, float y, float z); + +protected: + PannerNode(JS::Realm&, GC::Ref, PannerOptions const& = {}); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + +private: + // https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel + Bindings::PanningModelType m_panning_model { Bindings::PanningModelType::Equalpower }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionx + GC::Ref m_position_x; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positiony + GC::Ref m_position_y; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionz + GC::Ref m_position_z; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationx + GC::Ref m_orientation_x; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationy + GC::Ref m_orientation_y; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationz + GC::Ref m_orientation_z; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel + Bindings::DistanceModelType m_distance_model { Bindings::DistanceModelType::Inverse }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + double m_ref_distance { 1.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + double m_max_distance { 10000.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + double m_rolloff_factor { 1.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle + double m_cone_inner_angle { 360.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle + double m_cone_outer_angle { 360.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + double m_cone_outer_gain { 0.0 }; +}; + +} diff --git a/Libraries/LibWeb/WebAudio/PannerNode.idl b/Libraries/LibWeb/WebAudio/PannerNode.idl new file mode 100644 index 0000000000000..39213a1ec570a --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.idl @@ -0,0 +1,56 @@ +#import +#import +#import + +// https://webaudio.github.io/web-audio-api/#enumdef-panningmodeltype +enum PanningModelType { + "equalpower", + "HRTF" +}; + +// https://webaudio.github.io/web-audio-api/#enumdef-distancemodeltype +enum DistanceModelType { + "linear", + "inverse", + "exponential" +}; + +// https://webaudio.github.io/web-audio-api/#PannerOptions +dictionary PannerOptions : AudioNodeOptions { + PanningModelType panningModel = "equalpower"; + DistanceModelType distanceModel = "inverse"; + float positionX = 0; + float positionY = 0; + float positionZ = 0; + float orientationX = 1; + float orientationY = 0; + float orientationZ = 0; + double refDistance = 1; + double maxDistance = 10000; + double rolloffFactor = 1; + double coneInnerAngle = 360; + double coneOuterAngle = 360; + double coneOuterGain = 0; +}; + +// https://webaudio.github.io/web-audio-api/#PannerNode +[Exposed=Window] +interface PannerNode : AudioNode { + constructor(BaseAudioContext context, optional PannerOptions options = {}); + attribute PanningModelType panningModel; + readonly attribute AudioParam positionX; + readonly attribute AudioParam positionY; + readonly attribute AudioParam positionZ; + readonly attribute AudioParam orientationX; + readonly attribute AudioParam orientationY; + readonly attribute AudioParam orientationZ; + attribute DistanceModelType distanceModel; + attribute double refDistance; + attribute double maxDistance; + attribute double rolloffFactor; + attribute double coneInnerAngle; + attribute double coneOuterAngle; + attribute double coneOuterGain; + undefined setPosition(float x, float y, float z); + undefined setOrientation(float x, float y, float z); +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index b85359fbd6d6c..83d0aad42ea21 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -368,6 +368,7 @@ libweb_js_bindings(WebAudio/GainNode) libweb_js_bindings(WebAudio/ChannelMergerNode) libweb_js_bindings(WebAudio/OfflineAudioContext) libweb_js_bindings(WebAudio/OscillatorNode) +libweb_js_bindings(WebAudio/PannerNode) libweb_js_bindings(WebAudio/PeriodicWave) libweb_js_bindings(WebGL/WebGL2RenderingContext) libweb_js_bindings(WebGL/WebGLActiveInfo) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 4eab6f474f75d..52d5ae7ddfb3f 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -261,6 +261,7 @@ OfflineAudioContext Option OscillatorNode PageTransitionEvent +PannerNode Path2D Performance PerformanceEntry From 3063be11a91e78c1281cacaca696e8255b36edf1 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 13 Dec 2024 21:41:06 +0000 Subject: [PATCH 221/237] LibWeb/WebAudio: Implement BaseAudioContext#createPanner Required by Unity Web games. --- .../LibWeb/WebAudio/BaseAudioContext.cpp | 8 ++ Libraries/LibWeb/WebAudio/BaseAudioContext.h | 1 + .../LibWeb/WebAudio/BaseAudioContext.idl | 3 +- .../Text/expected/WebAudio/PannerNode.txt | 46 +++++++++ .../Text/input/WebAudio/PannerNode.html | 99 +++++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/WebAudio/PannerNode.txt create mode 100644 Tests/LibWeb/Text/input/WebAudio/PannerNode.html diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp b/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp index 11326d7b584f3..8e47f721d5b6b 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,13 @@ WebIDL::ExceptionOr> BaseAudioContext::create_gain() return GainNode::create(realm(), *this); } +// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner +WebIDL::ExceptionOr> BaseAudioContext::create_panner() +{ + // Factory method for a PannerNode. + return PannerNode::create(realm(), *this); +} + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer WebIDL::ExceptionOr BaseAudioContext::verify_audio_options_inside_nominal_range(JS::Realm& realm, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate) { diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.h b/Libraries/LibWeb/WebAudio/BaseAudioContext.h index 9487e94b36d06..c798ef38c8095 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.h +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.h @@ -62,6 +62,7 @@ class BaseAudioContext : public DOM::EventTarget { WebIDL::ExceptionOr> create_oscillator(); WebIDL::ExceptionOr> create_dynamics_compressor(); WebIDL::ExceptionOr> create_gain(); + WebIDL::ExceptionOr> create_panner(); GC::Ref decode_audio_data(GC::Root, GC::Ptr, GC::Ptr); diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.idl b/Libraries/LibWeb/WebAudio/BaseAudioContext.idl index 0153136015d73..98afc2544a2c1 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.idl +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.idl @@ -8,6 +8,7 @@ #import #import #import +#import #import // https://www.w3.org/TR/webaudio/#enumdef-audiocontextstate @@ -42,7 +43,7 @@ interface BaseAudioContext : EventTarget { GainNode createGain(); [FIXME] IIRFilterNode createIIRFilter (sequence feedforward, sequence feedback); OscillatorNode createOscillator(); - [FIXME] PannerNode createPanner (); + PannerNode createPanner(); [FIXME] PeriodicWave createPeriodicWave (sequence real, sequence imag, optional PeriodicWaveConstraints constraints = {}); [FIXME] ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0, optional unsigned long numberOfInputChannels = 2, optional unsigned long numberOfOutputChannels = 2); [FIXME] StereoPannerNode createStereoPanner (); diff --git a/Tests/LibWeb/Text/expected/WebAudio/PannerNode.txt b/Tests/LibWeb/Text/expected/WebAudio/PannerNode.txt new file mode 100644 index 0000000000000..e61103a78d463 --- /dev/null +++ b/Tests/LibWeb/Text/expected/WebAudio/PannerNode.txt @@ -0,0 +1,46 @@ +PannerNode +AudioNode +EventTarget +Object +[object AudioParam] current: 0, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 0, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 0, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 1, default: 1, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 1, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 1, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 1, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 0, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 0, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -52, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: 100000, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +[object AudioParam] current: -22051, default: 0, min: -3.4028234663852886e+38, max: 3.4028234663852886e+38, rate: a-rate +default panningModel: equalpower +default distanceModel: inverse +default refDistance: 1 +default maxDistance: 10000 +default rolloffFactor: 1 +default coneInnerAngle: 360 +default coneOuterAngle: 360 +default coneOuterGain: 0 +Did throw RangeError: refDistance cannot be negative +Did throw RangeError: rolloffFactor cannot be negative +Did throw RangeError: maxDistance cannot be negative +Did throw InvalidStateError: coneOuterGain must be in the range of [0, 1] +Did throw InvalidStateError: coneOuterGain must be in the range of [0, 1] +Did throw RangeError: refDistance cannot be negative +Did throw RangeError: rolloffFactor cannot be negative +Did throw RangeError: maxDistance cannot be negative +Did throw InvalidStateError: coneOuterGain must be in the range of [0, 1] +Did throw InvalidStateError: coneOuterGain must be in the range of [0, 1] diff --git a/Tests/LibWeb/Text/input/WebAudio/PannerNode.html b/Tests/LibWeb/Text/input/WebAudio/PannerNode.html new file mode 100644 index 0000000000000..81a540b56697a --- /dev/null +++ b/Tests/LibWeb/Text/input/WebAudio/PannerNode.html @@ -0,0 +1,99 @@ + + From e0c0668f3d7e6310800915323b29408cd6490128 Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Thu, 12 Dec 2024 10:26:41 -0800 Subject: [PATCH 222/237] LibWeb: Avoid re-encoding response headers isomorphic encoding a value that has already been encoded will result in garbage data. `response_headers` is already encoded in ISO-8859-1/latin1, we cannot use `from_string_pair`, as it triggers ISO-8859-1/latin1 encoding. Follow-up of https://github.com/LadybirdBrowser/ladybird/pull/1893 --- Libraries/LibWeb/Fetch/Fetching/Fetching.cpp | 6 +++--- Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.cpp | 8 ++++++++ Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.h | 1 + Libraries/LibWeb/Infra/Strings.cpp | 10 +++++++--- .../Text/expected/http-non-ascii-content-type.txt | 1 + .../LibWeb/Text/input/http-non-ascii-content-type.html | 3 ++- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index a576937d1dda7..9da3be3cfdb23 100644 --- a/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -2330,7 +2330,7 @@ WebIDL::ExceptionOr> nonstandard_resource_loader_file_o } for (auto const& [name, value] : response_headers.headers()) { - auto header = Infrastructure::Header::from_string_pair(name, value); + auto header = Infrastructure::Header::from_latin1_pair(name, value); response->header_list()->append(move(header)); } @@ -2396,7 +2396,7 @@ WebIDL::ExceptionOr> nonstandard_resource_loader_file_o response->set_status(status_code.value_or(200)); response->set_body(move(body)); for (auto const& [name, value] : response_headers.headers()) { - auto header = Infrastructure::Header::from_string_pair(name, value); + auto header = Infrastructure::Header::from_latin1_pair(name, value); response->header_list()->append(move(header)); } @@ -2421,7 +2421,7 @@ WebIDL::ExceptionOr> nonstandard_resource_loader_file_o auto [body, _] = TRY_OR_IGNORE(extract_body(realm, data)); response->set_body(move(body)); for (auto const& [name, value] : response_headers.headers()) { - auto header = Infrastructure::Header::from_string_pair(name, value); + auto header = Infrastructure::Header::from_latin1_pair(name, value); response->header_list()->append(move(header)); } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.cpp b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.cpp index 8f2be78b19ffa..c8e1d98c291b4 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.cpp @@ -58,6 +58,14 @@ Header Header::from_string_pair(StringView name, StringView value) }; } +Header Header::from_latin1_pair(StringView name, StringView value) +{ + return Header { + .name = MUST(ByteBuffer::copy(name.bytes())), + .value = MUST(ByteBuffer::copy(value.bytes())), + }; +} + GC::Ref HeaderList::create(JS::VM& vm) { return vm.heap().allocate(); diff --git a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.h b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.h index f032aeb1fa257..7dc9a6c508288 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Headers.h @@ -29,6 +29,7 @@ struct Header { [[nodiscard]] static Header copy(Header const&); [[nodiscard]] static Header from_string_pair(StringView, StringView); + [[nodiscard]] static Header from_latin1_pair(StringView, StringView); }; // https://fetch.spec.whatwg.org/#concept-header-list diff --git a/Libraries/LibWeb/Infra/Strings.cpp b/Libraries/LibWeb/Infra/Strings.cpp index 2b71dffbf699e..5e488c4cc17e9 100644 --- a/Libraries/LibWeb/Infra/Strings.cpp +++ b/Libraries/LibWeb/Infra/Strings.cpp @@ -147,11 +147,12 @@ ErrorOr to_ascii_uppercase(StringView string) // https://infra.spec.whatwg.org/#isomorphic-encode ByteBuffer isomorphic_encode(StringView input) { + // To isomorphic encode an isomorphic string input: return a byte sequence whose length is equal to input’s code + // point length and whose bytes have the same values as the values of input’s code points, in the same order. + // NOTE: This is essentially spec-speak for "Encode as ISO-8859-1 / Latin-1". ByteBuffer buf = {}; for (auto code_point : Utf8View { input }) { - // VERIFY(code_point <= 0xFF); - if (code_point > 0xFF) - dbgln("FIXME: Trying to isomorphic encode a string with code points > U+00FF."); + VERIFY(code_point <= 0xFF); buf.append((u8)code_point); } return buf; @@ -160,6 +161,9 @@ ByteBuffer isomorphic_encode(StringView input) // https://infra.spec.whatwg.org/#isomorphic-decode String isomorphic_decode(ReadonlyBytes input) { + // To isomorphic decode a byte sequence input, return a string whose code point length is equal + // to input’s length and whose code points have the same values as the values of input’s bytes, in the same order. + // NOTE: This is essentially spec-speak for "Decode as ISO-8859-1 / Latin-1". StringBuilder builder(input.size()); for (u8 code_point : input) { builder.append_code_point(code_point); diff --git a/Tests/LibWeb/Text/expected/http-non-ascii-content-type.txt b/Tests/LibWeb/Text/expected/http-non-ascii-content-type.txt index aaecaf93c4a5b..b69ff2c8a83e4 100644 --- a/Tests/LibWeb/Text/expected/http-non-ascii-content-type.txt +++ b/Tests/LibWeb/Text/expected/http-non-ascii-content-type.txt @@ -1 +1,2 @@ +Content-Type:text/html;test=ÿ;charset=gbk PASS (didn't crash) diff --git a/Tests/LibWeb/Text/input/http-non-ascii-content-type.html b/Tests/LibWeb/Text/input/http-non-ascii-content-type.html index d37a392ec369a..a725ea8370553 100644 --- a/Tests/LibWeb/Text/input/http-non-ascii-content-type.html +++ b/Tests/LibWeb/Text/input/http-non-ascii-content-type.html @@ -12,7 +12,8 @@ }, }); - const blob = await fetch(url).then((response) => response.blob()); + const headers = await fetch(url).then((response) => response.headers); + println("Content-Type:" + headers.get("Content-Type")); println("PASS (didn't crash)"); } catch (err) { println("FAIL - " + err); From 9ccaa3b7eb1a8985eb403541c6f99e3f2594c62e Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Tue, 17 Dec 2024 13:07:43 +0100 Subject: [PATCH 223/237] CI: Automatically label PRs with merge conflicts This action adds and removes the new "conflicts" label to indicate whether a pull request is in need of conflict resolution. This both automatically informs the author of the PR that this is the case, if they have notifications enabled that is, and makes for an easier evaluation of the PR queue. --- .github/workflows/merge-conflict-labeler.yml | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/merge-conflict-labeler.yml diff --git a/.github/workflows/merge-conflict-labeler.yml b/.github/workflows/merge-conflict-labeler.yml new file mode 100644 index 0000000000000..1c23867b96706 --- /dev/null +++ b/.github/workflows/merge-conflict-labeler.yml @@ -0,0 +1,22 @@ +name: 'Label PRs with merge conflicts' +on: + # PRs typically get conflicted after a push to master. + push: + branches: [master] + + # If a PR targeting master is (re)opened or updated, recheck for conflicts and update the label. + # NOTE: This runs against the target branch, not the PR branch. + pull_request_target: + types: [opened, synchronize, reopened] + branches: [master] + +jobs: + auto-labeler: + runs-on: ubuntu-24.04 + steps: + - uses: mschilde/auto-label-merge-conflicts@591722e97f3c4142df3eca156ed0dcf2bcd362bd + with: + CONFLICT_LABEL_NAME: 'conflicts' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAX_RETRIES: 3 + WAIT_MS: 15000 From 1b01464e41d23c66dac19e7bb718d5f03d7b5ac0 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Tue, 17 Dec 2024 13:58:34 +0100 Subject: [PATCH 224/237] CI: Add permissions to merge conflict labeler This was not necessary in my own test repository, but maybe we need it here regardless. --- .github/workflows/merge-conflict-labeler.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/merge-conflict-labeler.yml b/.github/workflows/merge-conflict-labeler.yml index 1c23867b96706..9d07c0aac02d5 100644 --- a/.github/workflows/merge-conflict-labeler.yml +++ b/.github/workflows/merge-conflict-labeler.yml @@ -13,6 +13,9 @@ on: jobs: auto-labeler: runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: write steps: - uses: mschilde/auto-label-merge-conflicts@591722e97f3c4142df3eca156ed0dcf2bcd362bd with: From 8ddd9ea5f4af0ffac170cc489b80ad0976c19a1c Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 12 Dec 2024 11:30:04 +0100 Subject: [PATCH 225/237] CI: Convert paths-ignore into separate job that checks changed paths We need to invoke the CI jobs every time, even if nothing relevant has changed, because we marked them as required status checks for PRs. If they are not invoked, the associated status checks remain in a 'pending' state indefinitely not allowing us to (auto-)merge the PR. --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7969e1df185d..a11f2ddf1ff21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,22 +1,40 @@ name: CI -on: - push: - paths-ignore: - - 'Documentation/**' - - '*.md' - pull_request: - paths-ignore: - - 'Documentation/**' - - '*.md' +on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || format('{0}-{1}', github.ref, github.run_number) }} cancel-in-progress: true jobs: + # Look at changed paths in the commit or PR to determine whether we need to run the complete CI job. If only + # documentation or .md files were changed, we wouldn't need to compile and check anything here. + path-changes: + runs-on: ubuntu-24.04 + permissions: + pull-requests: read + outputs: + source_excluding_docs: ${{ steps.filter.outputs.source_excluding_docs }} + steps: + # Only need a checkout if we're not running as part of a PR + - uses: actions/checkout@v4 + if: github.event_name != 'pull_request' + + # FIXME: change into `dorny/paths-filter@v3` when https://github.com/dorny/paths-filter/pull/226 is merged + - uses: petermetz/paths-filter@5ee2f5d4cf5d7bdd998a314a42da307e2ae1639d + id: filter + with: + predicate-quantifier: every # all globs below must match + filters: | + source_excluding_docs: + - '**' + - '!Documentation/**' + - '!*.md' + + # CI matrix - runs the job in lagom-template.yml with different configurations. Lagom: - if: github.repository == 'LadybirdBrowser/ladybird' + needs: path-changes + if: github.repository == 'LadybirdBrowser/ladybird' && needs.path-changes.outputs.source_excluding_docs == 'true' strategy: fail-fast: false From b00bb05b6be4bcbb3cc95d0b1e773e19c84555b5 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 12 Dec 2024 11:56:40 +0100 Subject: [PATCH 226/237] CI: Refactor branch matching in jobs Matching `master` can be a bit simpler and prevent invoking the job altogether. Additionally, we don't need an `always()` here: we don't have job dependencies and we want these jobs to be cancelable. --- .github/workflows/ladybird-js-artifacts.yml | 6 ++++-- .github/workflows/libjs-test262.yml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ladybird-js-artifacts.yml b/.github/workflows/ladybird-js-artifacts.yml index 5c9d286fd88a2..64155754eb504 100644 --- a/.github/workflows/ladybird-js-artifacts.yml +++ b/.github/workflows/ladybird-js-artifacts.yml @@ -1,6 +1,8 @@ name: Package the js repl as a binary artifact -on: [push] +on: + push: + branches: [master] env: LADYBIRD_SOURCE_DIR: ${{ github.workspace }} @@ -11,7 +13,7 @@ env: jobs: build-and-package: runs-on: ${{ matrix.os }} - if: always() && github.repository == 'LadybirdBrowser/ladybird' && github.ref == 'refs/heads/master' + if: github.repository == 'LadybirdBrowser/ladybird' strategy: fail-fast: false matrix: diff --git a/.github/workflows/libjs-test262.yml b/.github/workflows/libjs-test262.yml index 6a3b07610ac38..14283a487b091 100644 --- a/.github/workflows/libjs-test262.yml +++ b/.github/workflows/libjs-test262.yml @@ -1,6 +1,8 @@ name: Run test262 and test-wasm -on: [push] +on: + push: + branches: [master] env: LADYBIRD_SOURCE_DIR: ${{ github.workspace }} @@ -9,7 +11,7 @@ env: jobs: run_and_update_results: runs-on: test262-runner - if: always() && github.repository == 'LadybirdBrowser/ladybird' && github.ref == 'refs/heads/master' + if: github.repository == 'LadybirdBrowser/ladybird' concurrency: libjs-test262 From cf9bf5955032d6ce66f7ac550884aca7e20f56e7 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 12 Dec 2024 12:06:20 +0100 Subject: [PATCH 227/237] CI: Remove branch conditional from nightly Android build Apart from the fact that this workflow is failing every time, we don't need to check the branch we're on since it's only invoked for the default branch of the repository. We can also remove the `always()` since there are no job dependencies nor do we want this to be uncancelable. --- .github/workflows/nightly-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-android.yml b/.github/workflows/nightly-android.yml index e1c752a919904..56ab72b104dc6 100644 --- a/.github/workflows/nightly-android.yml +++ b/.github/workflows/nightly-android.yml @@ -20,7 +20,7 @@ concurrency: jobs: CI: runs-on: ${{ matrix.os }} - if: always() && github.repository == 'LadybirdBrowser/ladybird' && github.ref == 'refs/heads/master' + if: github.repository == 'LadybirdBrowser/ladybird' strategy: fail-fast: false matrix: From 25e05e76c45f505481b94511eef8747d8a75a7f5 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Thu, 12 Dec 2024 12:08:29 +0100 Subject: [PATCH 228/237] CI: Remove `always()` from job conditionals These jobs have no dependencies on other jobs, nor should they be uncancelable. --- .github/workflows/lint-code.yml | 2 +- .github/workflows/lint-commits.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-code.yml b/.github/workflows/lint-code.yml index 7d585899a8e5b..47a30e4165290 100644 --- a/.github/workflows/lint-code.yml +++ b/.github/workflows/lint-code.yml @@ -5,7 +5,7 @@ on: [ push, pull_request ] jobs: lint: runs-on: macos-14 - if: always() && github.repository == 'LadybirdBrowser/ladybird' + if: github.repository == 'LadybirdBrowser/ladybird' steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/lint-commits.yml b/.github/workflows/lint-commits.yml index b4d27a8e0c85a..e27b511ccae49 100644 --- a/.github/workflows/lint-commits.yml +++ b/.github/workflows/lint-commits.yml @@ -8,7 +8,7 @@ on: [pull_request_target] jobs: lint: runs-on: ubuntu-24.04 - if: always() && github.repository == 'LadybirdBrowser/ladybird' + if: github.repository == 'LadybirdBrowser/ladybird' steps: - name: Lint PR commits From 03aafda788ff36e31d4d58b58dee83cce3bb2c06 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Tue, 17 Dec 2024 14:53:42 +0100 Subject: [PATCH 229/237] CI: Post comment when PR has a merge conflict Switch out the action with another one that actually supports posting comments :^) --- .github/workflows/merge-conflict-labeler.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-conflict-labeler.yml b/.github/workflows/merge-conflict-labeler.yml index 9d07c0aac02d5..c84755cd902ab 100644 --- a/.github/workflows/merge-conflict-labeler.yml +++ b/.github/workflows/merge-conflict-labeler.yml @@ -17,9 +17,12 @@ jobs: contents: read pull-requests: write steps: - - uses: mschilde/auto-label-merge-conflicts@591722e97f3c4142df3eca156ed0dcf2bcd362bd + - uses: eps1lon/actions-label-merge-conflict@v3 with: - CONFLICT_LABEL_NAME: 'conflicts' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MAX_RETRIES: 3 - WAIT_MS: 15000 + commentOnDirty: > + Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to + [rebase](https://www.youtube.com/watch?v=ElRzTuYln0M) your branch on top of the latest `master`. + dirtyLabel: 'conflicts' + repoToken: ${{ secrets.GITHUB_TOKEN }} + retryAfter: 15 + retryMax: 3 From 6c691ccddc3eb6a154e8e769e94821d88e6bea6c Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 11 Dec 2024 15:34:31 +1300 Subject: [PATCH 230/237] LibWeb/HTML: Add null handling for "get noopener for window open" See: https://github.com/whatwg/html/pull/10847 Which also fixes a crash when window.open is given a blob URL which is not present in the Blob URL registry. --- Libraries/LibWeb/HTML/Window.cpp | 11 +++++------ .../HTML/Window-open-blob-url-without-blob-entry.txt | 1 + .../HTML/Window-open-blob-url-without-blob-entry.html | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/Window-open-blob-url-without-blob-entry.txt create mode 100644 Tests/LibWeb/Text/input/HTML/Window-open-blob-url-without-blob-entry.html diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index c57ff134a81e5..45acef00cad6c 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -155,12 +155,12 @@ WebIDL::ExceptionOr> Window::window_open_steps(StringView u } // https://html.spec.whatwg.org/multipage/nav-history-apis.html#get-noopener-for-window-open -static TokenizedFeature::NoOpener get_noopener_for_window_open(DOM::Document const& source_document, TokenizedFeature::Map const& tokenized_features, URL::URL url) +static TokenizedFeature::NoOpener get_noopener_for_window_open(DOM::Document const& source_document, TokenizedFeature::Map const& tokenized_features, Optional const& url) { - // 1. If url's scheme is "blob": - if (url.scheme() == "blob"sv) { + // 1. If url is not null and url's blob URL entry is not null: + if (url.has_value() && url->blob_url_entry().has_value()) { // 1. Let blobOrigin be url's blob URL entry's environment's origin. - auto blob_origin = url.blob_url_entry()->environment_origin; + auto blob_origin = url->blob_url_entry()->environment_origin; // 2. Let topLevelOrigin be sourceDocument's relevant settings object's top-level origin. auto top_level_origin = source_document.relevant_settings_object().top_level_origin; @@ -221,8 +221,7 @@ WebIDL::ExceptionOr Window::window_open_steps_internal(Str } // 9. Let noopener be the result of getting noopener for window open with sourceDocument, tokenizedFeatures, and urlRecord. - // FIXME: Spec bug: https://github.com/whatwg/html/issues/10844 - auto no_opener = get_noopener_for_window_open(source_document, tokenized_features, url_record.has_value() ? *url_record : URL::URL("about:blank")); + auto no_opener = get_noopener_for_window_open(source_document, tokenized_features, url_record); // 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"]. tokenized_features.remove("noopener"sv); diff --git a/Tests/LibWeb/Text/expected/HTML/Window-open-blob-url-without-blob-entry.txt b/Tests/LibWeb/Text/expected/HTML/Window-open-blob-url-without-blob-entry.txt new file mode 100644 index 0000000000000..50586a4cbfb7c --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/Window-open-blob-url-without-blob-entry.txt @@ -0,0 +1 @@ +PASS! (Didn't crash) diff --git a/Tests/LibWeb/Text/input/HTML/Window-open-blob-url-without-blob-entry.html b/Tests/LibWeb/Text/input/HTML/Window-open-blob-url-without-blob-entry.html new file mode 100644 index 0000000000000..3b5f9eb4dad5a --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/Window-open-blob-url-without-blob-entry.html @@ -0,0 +1,7 @@ + + From 8bd94e2593a550283941b2f6af5cf92dd21eb626 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 7 Nov 2024 16:27:04 +0100 Subject: [PATCH 231/237] LibWeb: Add font-variant-* css properties --- Libraries/LibWeb/CSS/Enums.json | 59 ++++++++++++++++++++++++- Libraries/LibWeb/CSS/Keywords.json | 31 +++++++++++++ Libraries/LibWeb/CSS/Properties.json | 66 +++++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/CSS/Enums.json b/Libraries/LibWeb/CSS/Enums.json index 432ebc382d7e9..6bc9d84bed0f3 100644 --- a/Libraries/LibWeb/CSS/Enums.json +++ b/Libraries/LibWeb/CSS/Enums.json @@ -221,9 +221,64 @@ "fallback", "optional" ], - "font-variant": [ + "font-variant-alternates": [ "normal", - "small-caps" + "historical-forms" + ], + "font-variant-caps": [ + "normal", + "small-caps", + "all-small-caps", + "petite-caps", + "all-petite-caps", + "unicase", + "titling-caps" + ], + "font-variant-east-asian": [ + "normal", + "ruby", + "jis78", + "jis83", + "jis90", + "jis04", + "simplified", + "traditional", + "full-width", + "proportional-width" + ], + "font-variant-emoji": [ + "normal", + "text", + "emoji", + "unicode" + ], + "font-variant-ligatures": [ + "normal", + "none", + "common-ligatures", + "no-common-ligatures", + "discretionary-ligatures", + "no-discretionary-ligatures", + "historical-ligatures", + "no-historical-ligatures", + "contextual", + "no-contextual" + ], + "font-variant-numeric": [ + "normal", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions" + ], + "font-variant-position": [ + "normal", + "sub", + "super" ], "font-width": [ "ultra-condensed", diff --git a/Libraries/LibWeb/CSS/Keywords.json b/Libraries/LibWeb/CSS/Keywords.json index 177358a2893b2..e95576afa7732 100644 --- a/Libraries/LibWeb/CSS/Keywords.json +++ b/Libraries/LibWeb/CSS/Keywords.json @@ -67,7 +67,9 @@ "additive", "alias", "all", + "all-petite-caps", "all-scroll", + "all-small-caps", "alpha", "alternate", "alternate-reverse", @@ -114,6 +116,7 @@ "collapse", "column", "column-reverse", + "common-ligatures", "compact", "condensed", "contain", @@ -121,6 +124,7 @@ "content-box", "contents", "context-menu", + "contextual", "copy", "cover", "crisp-edges", @@ -133,7 +137,9 @@ "decimal", "decimal-leading-zero", "default", + "diagonal-fractions", "disc", + "discretionary-ligatures", "disclosure-closed", "disclosure-open", "distribute", @@ -147,6 +153,7 @@ "ease-out", "ellipsis", "embed", + "emoji", "enabled", "end", "evenodd", @@ -185,6 +192,8 @@ "high-quality", "highlight", "highlighttext", + "historical-forms", + "historical-ligatures", "horizontal-tb", "hover", "inactiveborder", @@ -213,6 +222,10 @@ "isolate", "isolate-override", "italic", + "jis04", + "jis78", + "jis83", + "jis90", "jump-both", "jump-end", "jump-none", @@ -229,6 +242,7 @@ "lighter", "line-through", "linear", + "lining-nums", "linktext", "list-item", "listbox", @@ -262,7 +276,11 @@ "nearest", "nesw-resize", "no-close-quote", + "no-common-ligatures", + "no-contextual", + "no-discretionary-ligatures", "no-drop", + "no-historical-ligatures", "no-open-quote", "no-preference", "no-repeat", @@ -276,12 +294,14 @@ "nwse-resize", "oblique", "off", + "oldstyle-nums", "on", "opaque", "open-quote", "optimizequality", "optimizespeed", "optional", + "ordinal", "outset", "outside", "overline", @@ -289,6 +309,7 @@ "padding-box", "paged", "paused", + "petite-caps", "pixelated", "plaintext", "pointer", @@ -299,6 +320,8 @@ "progress", "progress-bar", "progressive", + "proportional-nums", + "proportional-width", "push-button", "radio", "rec2020", @@ -342,6 +365,8 @@ "serif", "sideways-lr", "sideways-rl", + "simplified", + "slashed-zero", "slider-horizontal", "slow", "small", @@ -357,6 +382,7 @@ "square-button", "srgb", "stable", + "stacked-fractions", "standalone", "standard", "start", @@ -378,6 +404,7 @@ "table-header-group", "table-row", "table-row-group", + "tabular-nums", "text", "text-bottom", "text-top", @@ -385,6 +412,7 @@ "textfield", "thick", "thin", + "titling-caps", "threeddarkshadow", "threedface", "threedhighlight", @@ -392,6 +420,7 @@ "threedshadow", "to-zero", "top", + "traditional", "ui-monospace", "ui-rounded", "ui-sans-serif", @@ -399,6 +428,8 @@ "ultra-condensed", "ultra-expanded", "underline", + "unicase", + "unicode", "unsafe", "unset", "up", diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index 346c9e4ff681b..59d2246300e74 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -1243,11 +1243,75 @@ ] }, "font-variant": { + "inherited": true, + "initial": "normal", + "longhands": [ + "font-variant-alternates", + "font-variant-caps", + "font-variant-east-asian", + "font-variant-emoji", + "font-variant-ligatures", + "font-variant-numeric", + "font-variant-position" + ], + "valid-types": [ + "string" + ] + }, + "font-variant-alternates": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-alternates" + ] + }, + "font-variant-caps": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-caps" + ] + }, + "font-variant-east-asian": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-east-asian" + ] + }, + "font-variant-emoji": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-emoji" + ] + }, + "font-variant-ligatures": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-ligatures" + ] + }, + "font-variant-numeric": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-numeric" + ] + }, + "font-variant-position": { "animation-type": "discrete", "inherited": true, "initial": "normal", "valid-types": [ - "font-variant" + "font-variant-position" ] }, "font-variation-settings": { From aabbe876287d7f7ec83d2eb94aa39d3c7074eeea Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 5 Dec 2024 01:14:56 +0100 Subject: [PATCH 232/237] LibWeb: Parse font-variant-* css properties --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 477 +++++++++++++++++++++++++ Libraries/LibWeb/CSS/Parser/Parser.h | 7 + 2 files changed, 484 insertions(+) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index ca7932a8cd605..0ac42abb0a05f 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -6056,6 +6056,459 @@ RefPtr Parser::parse_font_variation_settings_value(TokenStream Parser::parse_font_variant(TokenStream& tokens) +{ + // 6.11 https://drafts.csswg.org/css-fonts/#propdef-font-variant + // normal | none | + // [ [ || || || ] + // || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] || + // [ FIXME: stylistic() || + // historical-forms || + // FIXME: styleset(#) || + // FIXME: character-variant(#) || + // FIXME: swash() || + // FIXME: ornaments() || + // FIXME: annotation() ] || + // [ || || || + // ordinal || slashed-zero ] || [ || || ruby ] || + // [ sub | super ] || [ text | emoji | unicode ] ] + + bool has_common_ligatures = false; + bool has_discretionary_ligatures = false; + bool has_historical_ligatures = false; + bool has_contextual = false; + bool has_numeric_figures = false; + bool has_numeric_spacing = false; + bool has_numeric_fractions = false; + bool has_numeric_ordinals = false; + bool has_numeric_slashed_zero = false; + bool has_east_asian_variant = false; + bool has_east_asian_width = false; + bool has_east_asian_ruby = false; + RefPtr alternates_value {}; + RefPtr caps_value {}; + RefPtr emoji_value {}; + RefPtr position_value {}; + StyleValueVector east_asian_values; + StyleValueVector ligatures_values; + StyleValueVector numeric_values; + + if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + // normal, do nothing + } else if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::None)) { + // none + ligatures_values.append(parsed_value.release_nonnull()); + } else { + + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + if (!value->is_keyword()) { + // FIXME: alternate functions such as stylistic() + return nullptr; + } + auto keyword = value->to_keyword(); + + switch (keyword) { + // = [ common-ligatures | no-common-ligatures ] + case Keyword::CommonLigatures: + case Keyword::NoCommonLigatures: + if (has_common_ligatures) + return nullptr; + ligatures_values.append(move(value)); + has_common_ligatures = true; + break; + // = [ discretionary-ligatures | no-discretionary-ligatures ] + case Keyword::DiscretionaryLigatures: + case Keyword::NoDiscretionaryLigatures: + if (has_discretionary_ligatures) + return nullptr; + ligatures_values.append(move(value)); + has_discretionary_ligatures = true; + break; + // = [ historical-ligatures | no-historical-ligatures ] + case Keyword::HistoricalLigatures: + case Keyword::NoHistoricalLigatures: + if (has_historical_ligatures) + return nullptr; + ligatures_values.append(move(value)); + has_historical_ligatures = true; + break; + // = [ contextual | no-contextual ] + case Keyword::Contextual: + case Keyword::NoContextual: + if (has_contextual) + return nullptr; + ligatures_values.append(move(value)); + has_contextual = true; + break; + // historical-forms + case Keyword::HistoricalForms: + if (alternates_value != nullptr) + return nullptr; + alternates_value = value.ptr(); + break; + // [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] + case Keyword::SmallCaps: + case Keyword::AllSmallCaps: + case Keyword::PetiteCaps: + case Keyword::AllPetiteCaps: + case Keyword::Unicase: + case Keyword::TitlingCaps: + if (caps_value != nullptr) + return nullptr; + caps_value = value.ptr(); + break; + // = [ lining-nums | oldstyle-nums ] + case Keyword::LiningNums: + case Keyword::OldstyleNums: + if (has_numeric_figures) + return nullptr; + numeric_values.append(move(value)); + has_numeric_figures = true; + break; + // = [ proportional-nums | tabular-nums ] + case Keyword::ProportionalNums: + case Keyword::TabularNums: + if (has_numeric_spacing) + return nullptr; + numeric_values.append(move(value)); + has_numeric_spacing = true; + break; + // = [ diagonal-fractions | stacked-fractions] + case Keyword::DiagonalFractions: + case Keyword::StackedFractions: + if (has_numeric_fractions) + return nullptr; + numeric_values.append(move(value)); + has_numeric_fractions = true; + break; + // ordinal + case Keyword::Ordinal: + if (has_numeric_ordinals) + return nullptr; + numeric_values.append(move(value)); + has_numeric_ordinals = true; + break; + case Keyword::SlashedZero: + if (has_numeric_slashed_zero) + return nullptr; + numeric_values.append(move(value)); + has_numeric_slashed_zero = true; + break; + // = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] + case Keyword::Jis78: + case Keyword::Jis83: + case Keyword::Jis90: + case Keyword::Jis04: + case Keyword::Simplified: + case Keyword::Traditional: + if (has_east_asian_variant) + return nullptr; + east_asian_values.append(move(value)); + has_east_asian_variant = true; + break; + // = [ full-width | proportional-width ] + case Keyword::FullWidth: + case Keyword::ProportionalWidth: + if (has_east_asian_width) + return nullptr; + east_asian_values.append(move(value)); + has_east_asian_width = true; + break; + // ruby + case Keyword::Ruby: + if (has_east_asian_ruby) + return nullptr; + east_asian_values.append(move(value)); + has_east_asian_ruby = true; + break; + // text | emoji | unicode + case Keyword::Text: + case Keyword::Emoji: + case Keyword::Unicode: + if (emoji_value != nullptr) + return nullptr; + emoji_value = value.ptr(); + break; + // sub | super + case Keyword::Sub: + case Keyword::Super: + if (position_value != nullptr) + return nullptr; + position_value = value.ptr(); + break; + default: + break; + } + } + } + + if (ligatures_values.is_empty()) + ligatures_values.append(CSSKeywordValue::create(Keyword::Normal)); + if (numeric_values.is_empty()) + numeric_values.append(CSSKeywordValue::create(Keyword::Normal)); + if (east_asian_values.is_empty()) + east_asian_values.append(CSSKeywordValue::create(Keyword::Normal)); + + return ShorthandStyleValue::create(PropertyID::FontVariant, + { PropertyID::FontVariantAlternates, + PropertyID::FontVariantCaps, + PropertyID::FontVariantEastAsian, + PropertyID::FontVariantEmoji, + PropertyID::FontVariantLigatures, + PropertyID::FontVariantNumeric, + PropertyID::FontVariantPosition }, + { + alternates_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : alternates_value.release_nonnull(), + caps_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : caps_value.release_nonnull(), + StyleValueList::create(move(east_asian_values), StyleValueList::Separator::Space), + emoji_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : emoji_value.release_nonnull(), + StyleValueList::create(move(ligatures_values), StyleValueList::Separator::Space), + StyleValueList::create(move(numeric_values), StyleValueList::Separator::Space), + position_value == nullptr ? CSSKeywordValue::create(Keyword::Normal) : position_value.release_nonnull(), + }); +} + +RefPtr Parser::parse_font_variant_alternates_value(TokenStream& tokens) +{ + // 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop + // normal | + // [ FIXME: stylistic() || + // historical-forms || + // FIXME: styleset(#) || + // FIXME: character-variant(#) || + // FIXME: swash() || + // FIXME: ornaments() || + // FIXME: annotation() ] + + // normal + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) + return normal; + + // historical-forms + // FIXME: Support this together with other values when we parse them. + if (auto historical_forms = parse_all_as_single_keyword_value(tokens, Keyword::HistoricalForms)) + return historical_forms; + + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-variant-alternate: parsing {} not implemented.", tokens.next_token().to_debug_string()); + return nullptr; +} + +// FIXME: This should not be needed, however http://wpt.live/css/css-fonts/font-variant-caps.html fails without it +RefPtr Parser::parse_font_variant_caps_value(TokenStream& tokens) +{ + // https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps + // normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps + + bool has_token = false; + while (tokens.has_next_token()) { + if (has_token) + break; + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + auto font_variant = keyword_to_font_variant_caps(value->to_keyword()); + if (font_variant.has_value()) { + return value; + } + break; + } + + return nullptr; +} + +RefPtr Parser::parse_font_variant_east_asian_value(TokenStream& tokens) +{ + // 6.10 https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian + // normal | [ || || ruby ] + // = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] + // = [ full-width | proportional-width ] + + StyleValueVector value_list; + bool has_ruby = false; + bool has_variant = false; + bool has_width = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + auto font_variant = keyword_to_font_variant_east_asian(value->to_keyword()); + if (!font_variant.has_value()) { + return nullptr; + } + auto keyword = value->to_keyword(); + if (keyword == Keyword::Ruby) { + if (has_ruby) + return nullptr; + has_ruby = true; + } else if (keyword == Keyword::FullWidth || keyword == Keyword::ProportionalWidth) { + if (has_width) + return nullptr; + has_width = true; + } else { + if (has_variant) + return nullptr; + has_variant = true; + } + value_list.append(move(value)); + } + } + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + +RefPtr Parser::parse_font_variant_ligatures_value(TokenStream& tokens) +{ + // 6.4 https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures + // normal | none | [ || || || ] + // = [ common-ligatures | no-common-ligatures ] + // = [ discretionary-ligatures | no-discretionary-ligatures ] + // = [ historical-ligatures | no-historical-ligatures ] + // = [ contextual | no-contextual ] + + StyleValueVector value_list; + bool has_common_ligatures = false; + bool has_discretionary_ligatures = false; + bool has_historical_ligatures = false; + bool has_contextual = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + // none | ... + } else if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None)) { + value_list.append(none.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + switch (value->to_keyword()) { + // = [ common-ligatures | no-common-ligatures ] + case Keyword::CommonLigatures: + case Keyword::NoCommonLigatures: + if (has_common_ligatures) + return nullptr; + has_common_ligatures = true; + break; + // = [ discretionary-ligatures | no-discretionary-ligatures ] + case Keyword::DiscretionaryLigatures: + case Keyword::NoDiscretionaryLigatures: + if (has_discretionary_ligatures) + return nullptr; + has_discretionary_ligatures = true; + break; + // = [ historical-ligatures | no-historical-ligatures ] + case Keyword::HistoricalLigatures: + case Keyword::NoHistoricalLigatures: + if (has_historical_ligatures) + return nullptr; + has_historical_ligatures = true; + break; + // = [ contextual | no-contextual ] + case Keyword::Contextual: + case Keyword::NoContextual: + if (has_contextual) + return nullptr; + has_contextual = true; + break; + default: + return nullptr; + } + value_list.append(move(value)); + } + } + + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + +RefPtr Parser::parse_font_variant_numeric_value(TokenStream& tokens) +{ + // 6.7 https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric + // normal | [ || || || ordinal || slashed-zero] + // = [ lining-nums | oldstyle-nums ] + // = [ proportional-nums | tabular-nums ] + // = [ diagonal-fractions | stacked-fractions ] + + StyleValueVector value_list; + bool has_figures = false; + bool has_spacing = false; + bool has_fractions = false; + bool has_ordinals = false; + bool has_slashed_zero = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + switch (value->to_keyword()) { + // ... || ordinal + case Keyword::Ordinal: + if (has_ordinals) + return nullptr; + has_ordinals = true; + break; + // ... || slashed-zero + case Keyword::SlashedZero: + if (has_slashed_zero) + return nullptr; + has_slashed_zero = true; + break; + // = [ lining-nums | oldstyle-nums ] + case Keyword::LiningNums: + case Keyword::OldstyleNums: + if (has_figures) + return nullptr; + has_figures = true; + break; + // = [ proportional-nums | tabular-nums ] + case Keyword::ProportionalNums: + case Keyword::TabularNums: + if (has_spacing) + return nullptr; + has_spacing = true; + break; + // = [ diagonal-fractions | stacked-fractions ] + case Keyword::DiagonalFractions: + case Keyword::StackedFractions: + if (has_fractions) + return nullptr; + has_fractions = true; + break; + default: + return nullptr; + } + value_list.append(value); + } + } + + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + Vector Parser::parse_as_font_face_src() { return parse_font_face_src(m_token_stream); @@ -8046,6 +8499,30 @@ Parser::ParseErrorOr> Parser::parse_css_value(Prope if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::FontVariant: + if (auto parsed_value = parse_font_variant(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantAlternates: + if (auto parsed_value = parse_font_variant_alternates_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantCaps: + if (auto parsed_value = parse_font_variant_caps_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantEastAsian: + if (auto parsed_value = parse_font_variant_east_asian_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantLigatures: + if (auto parsed_value = parse_font_variant_ligatures_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantNumeric: + if (auto parsed_value = parse_font_variant_numeric_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::GridArea: if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 87bd6cd300714..1a9367a55bc1a 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -330,6 +330,13 @@ class Parser { RefPtr parse_font_language_override_value(TokenStream&); RefPtr parse_font_feature_settings_value(TokenStream&); RefPtr parse_font_variation_settings_value(TokenStream&); + RefPtr parse_font_variant(TokenStream&); + RefPtr parse_font_variant_alternates_value(TokenStream&); + RefPtr parse_font_variant_caps_value(TokenStream&); + RefPtr parse_font_variant_east_asian_value(TokenStream&); + RefPtr parse_font_variant_emoji(TokenStream&); + RefPtr parse_font_variant_ligatures_value(TokenStream&); + RefPtr parse_font_variant_numeric_value(TokenStream&); RefPtr parse_list_style_value(TokenStream&); RefPtr parse_math_depth_value(TokenStream&); RefPtr parse_overflow_value(TokenStream&); From 1c42d6831b2032fa54b2961d2bdd18bf44f1da1e Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 5 Dec 2024 01:19:03 +0100 Subject: [PATCH 233/237] LibWeb: Style font-variant-* css properties --- Libraries/LibGfx/Font/FontVariant.h | 91 +++++ Libraries/LibWeb/CSS/CSSStyleValue.cpp | 167 ++++++++ Libraries/LibWeb/CSS/CSSStyleValue.h | 9 + Libraries/LibWeb/CSS/ComputedValues.h | 25 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 8 + Libraries/LibWeb/CSS/StyleProperties.cpp | 48 ++- Libraries/LibWeb/CSS/StyleProperties.h | 8 +- .../CSS/StyleValues/ShorthandStyleValue.cpp | 96 +++++ ...upported-properties-and-default-values.txt | 16 +- ...eclaration-has-indexed-property-getter.txt | 384 +++++++++--------- .../css/getComputedStyle-print-all.txt | 10 +- 11 files changed, 659 insertions(+), 203 deletions(-) create mode 100644 Libraries/LibGfx/Font/FontVariant.h diff --git a/Libraries/LibGfx/Font/FontVariant.h b/Libraries/LibGfx/Font/FontVariant.h new file mode 100644 index 0000000000000..51a1f89dc3824 --- /dev/null +++ b/Libraries/LibGfx/Font/FontVariant.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, Johan Dahlin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#pragma once + +namespace Gfx { + +struct FontVariantAlternates { + bool historical_forms { false }; +}; + +struct FontVariantEastAsian { + enum class Variant { + Unset, + Jis78, + Jis83, + Jis90, + Jis04, + Simplified, + Traditional + }; + enum class Width { + Unset, + Proportional, + FullWidth + }; + + bool ruby = false; + Variant variant { Variant::Unset }; + Width width { Width::Unset }; +}; + +struct FontVariantLigatures { + enum class Common { + Unset, + Common, + NoCommon + }; + enum class Discretionary { + Unset, + Discretionary, + NoDiscretionary + }; + enum class Historical { + Unset, + Historical, + NoHistorical + }; + enum class Contextual { + Unset, + Contextual, + NoContextual + }; + bool none = false; + Common common { Common::Unset }; + Discretionary discretionary { Discretionary::Unset }; + Historical historical { Historical::Unset }; + Contextual contextual { Contextual::Unset }; +}; + +struct FontVariantNumeric { + enum class Figure { + Unset, + Lining, + Oldstyle + }; + enum class Spacing { + Unset, + Proportional, + Tabular + }; + enum class Fraction { + Unset, + Diagonal, + Stacked + }; + bool ordinal = false; + bool slashed_zero = false; + Figure figure { Figure::Unset }; + Spacing spacing { Spacing::Unset }; + Fraction fraction { Fraction::Unset }; +}; + +} diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.cpp b/Libraries/LibWeb/CSS/CSSStyleValue.cpp index f3eeb55adb3a7..92ffd6d89620b 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleValue.cpp @@ -495,4 +495,171 @@ int CSSStyleValue::to_font_width() const return width; } +Optional CSSStyleValue::to_font_variant_alternates() const +{ + VERIFY(is_keyword()); + switch (as_keyword().keyword()) { + case Keyword::Normal: + return {}; + case Keyword::HistoricalForms: + return Gfx::FontVariantAlternates { .historical_forms = true }; + default: + VERIFY_NOT_REACHED(); + } +} + +Optional CSSStyleValue::to_font_variant_caps() const +{ + VERIFY(is_keyword()); + return keyword_to_font_variant_caps(as_keyword().keyword()); +} + +Optional CSSStyleValue::to_font_variant_east_asian() const +{ + VERIFY(is_value_list()); + auto& list = as_value_list(); + Gfx::FontVariantEastAsian east_asian {}; + for (auto& value : list.values()) { + VERIFY(value->is_keyword()); + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + return {}; + case Keyword::Jis78: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis78; + break; + case Keyword::Jis83: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis83; + break; + case Keyword::Jis90: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis90; + break; + case Keyword::Jis04: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis04; + break; + case Keyword::Simplified: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Simplified; + break; + case Keyword::Traditional: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Traditional; + break; + case Keyword::FullWidth: + east_asian.width = Gfx::FontVariantEastAsian::Width::FullWidth; + break; + case Keyword::ProportionalWidth: + east_asian.width = Gfx::FontVariantEastAsian::Width::Proportional; + break; + case Keyword::Ruby: + east_asian.ruby = true; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return east_asian; +} + +Optional CSSStyleValue::to_font_variant_emoji() const +{ + VERIFY(is_keyword()); + return keyword_to_font_variant_emoji(as_keyword().keyword()); +} + +Optional CSSStyleValue::to_font_variant_ligatures() const +{ + if (!is_value_list()) { + return {}; + } + auto const& list = as_value_list(); + Gfx::FontVariantLigatures ligatures {}; + + for (auto& value : list.values()) { + if (!value->is_keyword()) + continue; + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + return {}; + case Keyword::None: + ligatures.none = true; + return ligatures; + case Keyword::CommonLigatures: + ligatures.common = Gfx::FontVariantLigatures::Common::Common; + break; + case Keyword::NoCommonLigatures: + ligatures.common = Gfx::FontVariantLigatures::Common::NoCommon; + break; + case Keyword::DiscretionaryLigatures: + ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::Discretionary; + break; + case Keyword::NoDiscretionaryLigatures: + ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::NoDiscretionary; + break; + case Keyword::HistoricalLigatures: + ligatures.historical = Gfx::FontVariantLigatures::Historical::Historical; + break; + case Keyword::NoHistoricalLigatures: + ligatures.historical = Gfx::FontVariantLigatures::Historical::NoHistorical; + break; + case Keyword::Contextual: + ligatures.contextual = Gfx::FontVariantLigatures::Contextual::Contextual; + break; + case Keyword::NoContextual: + ligatures.contextual = Gfx::FontVariantLigatures::Contextual::NoContextual; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return ligatures; +} + +Optional CSSStyleValue::to_font_variant_numeric() const +{ + VERIFY(is_value_list()); + auto& list = as_value_list(); + Gfx::FontVariantNumeric numeric {}; + for (auto& value : list.values()) { + VERIFY(value->is_keyword()); + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + return {}; + case Keyword::Ordinal: + numeric.ordinal = true; + break; + case Keyword::SlashedZero: + numeric.slashed_zero = true; + break; + case Keyword::OldstyleNums: + numeric.figure = Gfx::FontVariantNumeric::Figure::Oldstyle; + break; + case Keyword::LiningNums: + numeric.figure = Gfx::FontVariantNumeric::Figure::Lining; + break; + case Keyword::ProportionalNums: + numeric.spacing = Gfx::FontVariantNumeric::Spacing::Proportional; + break; + case Keyword::TabularNums: + numeric.spacing = Gfx::FontVariantNumeric::Spacing::Tabular; + break; + case Keyword::DiagonalFractions: + numeric.fraction = Gfx::FontVariantNumeric::Fraction::Diagonal; + break; + case Keyword::StackedFractions: + numeric.fraction = Gfx::FontVariantNumeric::Fraction::Stacked; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return numeric; +} + +Optional CSSStyleValue::to_font_variant_position() const +{ + VERIFY(is_keyword()); + return keyword_to_font_variant_position(as_keyword().keyword()); +} + } diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.h b/Libraries/LibWeb/CSS/CSSStyleValue.h index 697be536ee298..535f993dd32de 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.h +++ b/Libraries/LibWeb/CSS/CSSStyleValue.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,7 @@ class CSSStyleValue : public RefCounted { Edge, FilterValueList, Flex, + FontVariant, Frequency, GridAutoFlow, GridTemplateArea, @@ -370,6 +372,13 @@ class CSSStyleValue : public RefCounted { [[nodiscard]] int to_font_weight() const; [[nodiscard]] int to_font_slope() const; [[nodiscard]] int to_font_width() const; + [[nodiscard]] Optional to_font_variant_alternates() const; + [[nodiscard]] Optional to_font_variant_caps() const; + [[nodiscard]] Optional to_font_variant_east_asian() const; + [[nodiscard]] Optional to_font_variant_emoji() const; + [[nodiscard]] Optional to_font_variant_ligatures() const; + [[nodiscard]] Optional to_font_variant_numeric() const; + [[nodiscard]] Optional to_font_variant_position() const; virtual bool equals(CSSStyleValue const& other) const = 0; diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index 39030e53a875c..be5324bf5f5ca 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -97,7 +97,6 @@ class InitialValues { static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; } static CSSPixels font_size() { return 16; } static int font_weight() { return 400; } - static CSS::FontVariant font_variant() { return CSS::FontVariant::Normal; } static CSSPixels line_height() { return 0; } static CSS::Float float_() { return CSS::Float::None; } static CSS::Length border_spacing() { return CSS::Length::make_px(0); } @@ -515,7 +514,13 @@ class ComputedValues { Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; } CSSPixels font_size() const { return m_inherited.font_size; } int font_weight() const { return m_inherited.font_weight; } - CSS::FontVariant font_variant() const { return m_inherited.font_variant; } + Optional font_variant_alternates() const { return m_inherited.font_variant_alternates; } + FontVariantCaps font_variant_caps() const { return m_inherited.font_variant_caps; } + Optional font_variant_east_asian() const { return m_inherited.font_variant_east_asian; } + FontVariantEmoji font_variant_emoji() const { return m_inherited.font_variant_emoji; } + Optional font_variant_ligatures() const { return m_inherited.font_variant_ligatures; } + Optional font_variant_numeric() const { return m_inherited.font_variant_numeric; } + FontVariantPosition font_variant_position() const { return m_inherited.font_variant_position; } Optional font_language_override() const { return m_inherited.font_language_override; } Optional> font_feature_settings() const { return m_inherited.font_feature_settings; } Optional> font_variation_settings() const { return m_inherited.font_variation_settings; } @@ -549,7 +554,13 @@ class ComputedValues { RefPtr font_list {}; CSSPixels font_size { InitialValues::font_size() }; int font_weight { InitialValues::font_weight() }; - CSS::FontVariant font_variant { InitialValues::font_variant() }; + Optional font_variant_alternates; + FontVariantCaps font_variant_caps { FontVariantCaps::Normal }; + Optional font_variant_east_asian; + FontVariantEmoji font_variant_emoji { FontVariantEmoji::Normal }; + Optional font_variant_ligatures; + Optional font_variant_numeric; + FontVariantPosition font_variant_position { FontVariantPosition::Normal }; Optional font_language_override; Optional> font_feature_settings; Optional> font_variation_settings; @@ -725,7 +736,13 @@ class MutableComputedValues final : public ComputedValues { void set_font_list(NonnullRefPtr font_list) { m_inherited.font_list = move(font_list); } void set_font_size(CSSPixels font_size) { m_inherited.font_size = font_size; } void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; } - void set_font_variant(CSS::FontVariant font_variant) { m_inherited.font_variant = font_variant; } + void set_font_variant_alternates(Optional font_variant_alternates) { m_inherited.font_variant_alternates = font_variant_alternates; } + void set_font_variant_caps(FontVariantCaps font_variant_caps) { m_inherited.font_variant_caps = font_variant_caps; } + void set_font_variant_east_asian(Optional font_variant_east_asian) { m_inherited.font_variant_east_asian = font_variant_east_asian; } + void set_font_variant_emoji(FontVariantEmoji font_variant_emoji) { m_inherited.font_variant_emoji = font_variant_emoji; } + void set_font_variant_ligatures(Optional font_variant_ligatures) { m_inherited.font_variant_ligatures = font_variant_ligatures; } + void set_font_variant_numeric(Optional font_variant_numeric) { m_inherited.font_variant_numeric = font_variant_numeric; } + void set_font_variant_position(FontVariantPosition font_variant_position) { m_inherited.font_variant_position = font_variant_position; } void set_font_language_override(Optional font_language_override) { m_inherited.font_language_override = font_language_override; } void set_font_feature_settings(Optional> value) { m_inherited.font_feature_settings = move(value); } void set_font_variation_settings(Optional> value) { m_inherited.font_variation_settings = move(value); } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 3683c181136b2..8b1ad246ca911 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -2110,6 +2110,14 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::LineHeight, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariant, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantAlternates, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantCaps, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantEmoji, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantEastAsian, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantLigatures, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantNumeric, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantPosition, pseudo_element); auto const& font_family = style.property(CSS::PropertyID::FontFamily); auto const& font_size = style.property(CSS::PropertyID::FontSize); diff --git a/Libraries/LibWeb/CSS/StyleProperties.cpp b/Libraries/LibWeb/CSS/StyleProperties.cpp index b67f09e9cde71..37511eb56e434 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -1173,12 +1173,6 @@ Variant StyleProperties::vertical_ali VERIFY_NOT_REACHED(); } -Optional StyleProperties::font_variant() const -{ - auto const& value = property(CSS::PropertyID::FontVariant); - return keyword_to_font_variant(value.to_keyword()); -} - Optional StyleProperties::font_language_override() const { auto const& value = property(CSS::PropertyID::FontLanguageOverride); @@ -1187,6 +1181,48 @@ Optional StyleProperties::font_language_override() const return {}; } +Optional StyleProperties::font_variant_alternates() const +{ + auto const& value = property(CSS::PropertyID::FontVariantAlternates); + return value.to_font_variant_alternates(); +} + +Optional StyleProperties::font_variant_caps() const +{ + auto const& value = property(CSS::PropertyID::FontVariantCaps); + return value.to_font_variant_caps(); +} + +Optional StyleProperties::font_variant_east_asian() const +{ + auto const& value = property(CSS::PropertyID::FontVariantEastAsian); + return value.to_font_variant_east_asian(); +} + +Optional StyleProperties::font_variant_emoji() const +{ + auto const& value = property(CSS::PropertyID::FontVariantEmoji); + return value.to_font_variant_emoji(); +} + +Optional StyleProperties::font_variant_ligatures() const +{ + auto const& value = property(CSS::PropertyID::FontVariantLigatures); + return value.to_font_variant_ligatures(); +} + +Optional StyleProperties::font_variant_numeric() const +{ + auto const& value = property(CSS::PropertyID::FontVariantNumeric); + return value.to_font_variant_numeric(); +} + +Optional StyleProperties::font_variant_position() const +{ + auto const& value = property(CSS::PropertyID::FontVariantPosition); + return value.to_font_variant_position(); +} + Optional> StyleProperties::font_feature_settings() const { auto const& value = property(PropertyID::FontFeatureSettings); diff --git a/Libraries/LibWeb/CSS/StyleProperties.h b/Libraries/LibWeb/CSS/StyleProperties.h index 4eb45869b40a4..5b7044fa88ac9 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Libraries/LibWeb/CSS/StyleProperties.h @@ -149,7 +149,13 @@ class StyleProperties { Optional box_sizing() const; Optional pointer_events() const; Variant vertical_align() const; - Optional font_variant() const; + Optional font_variant_alternates() const; + Optional font_variant_caps() const; + Optional font_variant_east_asian() const; + Optional font_variant_emoji() const; + Optional font_variant_ligatures() const; + Optional font_variant_numeric() const; + Optional font_variant_position() const; Optional font_language_override() const; Optional> font_feature_settings() const; Optional> font_variation_settings() const; diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 5f9963e224036..9193b660c5c73 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -173,6 +173,102 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const longhand(PropertyID::FontSize)->to_string(mode), longhand(PropertyID::LineHeight)->to_string(mode), longhand(PropertyID::FontFamily)->to_string(mode))); + case PropertyID::FontVariant: { + Vector values; + auto ligatures_or_null = longhand(PropertyID::FontVariantLigatures)->to_font_variant_ligatures(); + if (ligatures_or_null.has_value()) { + auto ligatures = ligatures_or_null.release_value(); + if (ligatures.none) { + return MUST(String::formatted(""sv)); + } else { + if (ligatures.common == Gfx::FontVariantLigatures::Common::Common) + values.append("common-ligatures"sv); + else if (ligatures.common == Gfx::FontVariantLigatures::Common::NoCommon) + values.append("no-common-ligatures"sv); + if (ligatures.discretionary == Gfx::FontVariantLigatures::Discretionary::Discretionary) + values.append("discretionary-ligatures"sv); + else if (ligatures.discretionary == Gfx::FontVariantLigatures::Discretionary::NoDiscretionary) + values.append("no-discretionary-ligatures"sv); + if (ligatures.historical == Gfx::FontVariantLigatures::Historical::Historical) + values.append("historical-ligatures"sv); + else if (ligatures.historical == Gfx::FontVariantLigatures::Historical::NoHistorical) + values.append("no-historical-ligatures"sv); + if (ligatures.contextual == Gfx::FontVariantLigatures::Contextual::Contextual) + values.append("contextual"sv); + else if (ligatures.contextual == Gfx::FontVariantLigatures::Contextual::NoContextual) + values.append("no-contextual"sv); + } + } + + auto caps_or_null = longhand(PropertyID::FontVariantCaps)->to_font_variant_caps(); + if (caps_or_null.has_value() && caps_or_null.value() != CSS::FontVariantCaps::Normal) { + values.append(CSS::to_string(caps_or_null.release_value())); + } + + auto emoji_or_null = longhand(PropertyID::FontVariantEmoji)->to_font_variant_emoji(); + if (emoji_or_null.has_value() && emoji_or_null.value() != CSS::FontVariantEmoji::Normal) { + values.append(CSS::to_string(emoji_or_null.release_value())); + } + + auto alternates_or_null = longhand(PropertyID::FontVariantAlternates)->to_font_variant_alternates(); + if (alternates_or_null.has_value()) + values.append("historical-forms"sv); + + auto numeric_or_null = longhand(PropertyID::FontVariantNumeric)->to_font_variant_numeric(); + if (numeric_or_null.has_value()) { + auto numeric = numeric_or_null.release_value(); + if (numeric.ordinal) + values.append("ordinal"sv); + if (numeric.slashed_zero) + values.append("slashed-zero"sv); + if (numeric.figure == Gfx::FontVariantNumeric::Figure::Oldstyle) + values.append("oldstyle-nums"sv); + else if (numeric.figure == Gfx::FontVariantNumeric::Figure::Lining) + values.append("lining-nums"sv); + if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Proportional) + values.append("proportional-nums"sv); + else if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Tabular) + values.append("tabular-nums"sv); + if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Diagonal) + values.append("diagonal-fractions"sv); + else if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Stacked) + values.append("stacked-fractions"sv); + } + auto east_asian_or_null = longhand(PropertyID::FontVariantEastAsian)->to_font_variant_east_asian(); + if (east_asian_or_null.has_value()) { + auto east_asian = east_asian_or_null.release_value(); + if (east_asian.ruby) + values.append("ruby"sv); + else { + if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Jis78) + values.append("jis78"sv); + else if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Jis83) + values.append("jis83"sv); + else if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Jis90) + values.append("jis90"sv); + else if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Jis04) + values.append("jis04"sv); + else if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Simplified) + values.append("simplified"sv); + else if (east_asian.variant == Gfx::FontVariantEastAsian::Variant::Traditional) + values.append("traditional"sv); + if (east_asian.width == Gfx::FontVariantEastAsian::Width::Proportional) + values.append("proportional-width"sv); + else if (east_asian.width == Gfx::FontVariantEastAsian::Width::FullWidth) + values.append("full-width"sv); + } + } + auto position_or_null = longhand(PropertyID::FontVariantPosition)->to_font_variant_position(); + if (position_or_null.has_value() && position_or_null.value() != CSS::FontVariantPosition::Normal) { + values.append(CSS::to_string(position_or_null.release_value())); + } + StringBuilder builder; + if (values.is_empty()) + builder.append("normal"sv); + else + builder.join(' ', values); + return MUST(builder.to_string()); + } case PropertyID::GridArea: { auto& row_start = longhand(PropertyID::GridRowStart)->as_grid_track_placement(); auto& column_start = longhand(PropertyID::GridColumnStart)->as_grid_track_placement(); diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt index b4a657217feae..aea9445aad3d2 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt @@ -1,6 +1,6 @@ All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle: 'cssText': '' -'length': '206' +'length': '212' 'parentRule': 'null' 'cssFloat': 'none' 'WebkitAlignContent': 'normal' @@ -305,6 +305,20 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'font-style': 'normal' 'fontVariant': 'normal' 'font-variant': 'normal' +'fontVariantAlternates': 'normal' +'font-variant-alternates': 'normal' +'fontVariantCaps': 'normal' +'font-variant-caps': 'normal' +'fontVariantEastAsian': 'normal' +'font-variant-east-asian': 'normal' +'fontVariantEmoji': 'normal' +'font-variant-emoji': 'normal' +'fontVariantLigatures': 'normal' +'font-variant-ligatures': 'normal' +'fontVariantNumeric': 'normal' +'font-variant-numeric': 'normal' +'fontVariantPosition': 'normal' +'font-variant-position': 'normal' 'fontVariationSettings': 'normal' 'font-variation-settings': 'normal' 'fontWeight': '400' diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt index 1354e1e73363f..06de5b7266e5d 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt @@ -17,195 +17,201 @@ All properties associated with getComputedStyle(document.body): "14": "font-language-override", "15": "font-size", "16": "font-style", - "17": "font-variant", - "18": "font-variation-settings", - "19": "font-weight", - "20": "font-width", - "21": "image-rendering", - "22": "letter-spacing", - "23": "line-height", - "24": "list-style-image", - "25": "list-style-position", - "26": "list-style-type", - "27": "math-depth", - "28": "math-shift", - "29": "math-style", - "30": "pointer-events", - "31": "quotes", - "32": "stroke", - "33": "stroke-dasharray", - "34": "stroke-dashoffset", - "35": "stroke-linecap", - "36": "stroke-linejoin", - "37": "stroke-miterlimit", - "38": "stroke-opacity", - "39": "stroke-width", - "40": "tab-size", - "41": "text-align", - "42": "text-anchor", - "43": "text-decoration-line", - "44": "text-indent", - "45": "text-justify", - "46": "text-shadow", - "47": "text-transform", - "48": "visibility", - "49": "white-space", - "50": "word-break", - "51": "word-spacing", - "52": "word-wrap", - "53": "writing-mode", - "54": "align-content", - "55": "align-items", - "56": "align-self", - "57": "animation-delay", - "58": "animation-direction", - "59": "animation-duration", - "60": "animation-fill-mode", - "61": "animation-iteration-count", - "62": "animation-name", - "63": "animation-play-state", - "64": "animation-timing-function", - "65": "appearance", - "66": "aspect-ratio", - "67": "backdrop-filter", - "68": "background-attachment", - "69": "background-clip", - "70": "background-color", - "71": "background-image", - "72": "background-origin", - "73": "background-position-x", - "74": "background-position-y", - "75": "background-repeat", - "76": "background-size", - "77": "border-bottom-color", - "78": "border-bottom-left-radius", - "79": "border-bottom-right-radius", - "80": "border-bottom-style", - "81": "border-bottom-width", - "82": "border-left-color", - "83": "border-left-style", - "84": "border-left-width", - "85": "border-right-color", - "86": "border-right-style", - "87": "border-right-width", - "88": "border-top-color", - "89": "border-top-left-radius", - "90": "border-top-right-radius", - "91": "border-top-style", - "92": "border-top-width", - "93": "bottom", - "94": "box-shadow", - "95": "box-sizing", - "96": "clear", - "97": "clip", - "98": "clip-path", - "99": "column-count", - "100": "column-gap", - "101": "column-span", - "102": "column-width", - "103": "content", - "104": "content-visibility", - "105": "counter-increment", - "106": "counter-reset", - "107": "counter-set", - "108": "cx", - "109": "cy", - "110": "display", - "111": "filter", - "112": "flex-basis", - "113": "flex-direction", - "114": "flex-grow", - "115": "flex-shrink", - "116": "flex-wrap", - "117": "float", - "118": "grid-auto-columns", - "119": "grid-auto-flow", - "120": "grid-auto-rows", - "121": "grid-column-end", - "122": "grid-column-start", - "123": "grid-row-end", - "124": "grid-row-start", - "125": "grid-template-areas", - "126": "grid-template-columns", - "127": "grid-template-rows", - "128": "height", - "129": "inline-size", - "130": "inset-block-end", - "131": "inset-block-start", - "132": "inset-inline-end", - "133": "inset-inline-start", - "134": "justify-content", - "135": "justify-items", - "136": "justify-self", - "137": "left", - "138": "margin-block-end", - "139": "margin-block-start", - "140": "margin-bottom", - "141": "margin-inline-end", - "142": "margin-inline-start", - "143": "margin-left", - "144": "margin-right", - "145": "margin-top", - "146": "mask", - "147": "mask-image", - "148": "mask-type", - "149": "max-height", - "150": "max-inline-size", - "151": "max-width", - "152": "min-height", - "153": "min-inline-size", - "154": "min-width", - "155": "object-fit", - "156": "object-position", - "157": "opacity", - "158": "order", - "159": "outline-color", - "160": "outline-offset", - "161": "outline-style", - "162": "outline-width", - "163": "overflow-x", - "164": "overflow-y", - "165": "padding-block-end", - "166": "padding-block-start", - "167": "padding-bottom", - "168": "padding-inline-end", - "169": "padding-inline-start", - "170": "padding-left", - "171": "padding-right", - "172": "padding-top", - "173": "position", - "174": "r", - "175": "right", - "176": "rotate", - "177": "row-gap", - "178": "rx", - "179": "ry", - "180": "scale", - "181": "scrollbar-gutter", - "182": "scrollbar-width", - "183": "stop-color", - "184": "stop-opacity", - "185": "table-layout", - "186": "text-decoration-color", - "187": "text-decoration-style", - "188": "text-decoration-thickness", - "189": "text-overflow", - "190": "top", - "191": "transform", - "192": "transform-box", - "193": "transform-origin", - "194": "transition-delay", - "195": "transition-duration", - "196": "transition-property", - "197": "transition-timing-function", - "198": "translate", - "199": "unicode-bidi", - "200": "user-select", - "201": "vertical-align", - "202": "width", - "203": "x", - "204": "y", - "205": "z-index" + "17": "font-variant-alternates", + "18": "font-variant-caps", + "19": "font-variant-east-asian", + "20": "font-variant-emoji", + "21": "font-variant-ligatures", + "22": "font-variant-numeric", + "23": "font-variant-position", + "24": "font-variation-settings", + "25": "font-weight", + "26": "font-width", + "27": "image-rendering", + "28": "letter-spacing", + "29": "line-height", + "30": "list-style-image", + "31": "list-style-position", + "32": "list-style-type", + "33": "math-depth", + "34": "math-shift", + "35": "math-style", + "36": "pointer-events", + "37": "quotes", + "38": "stroke", + "39": "stroke-dasharray", + "40": "stroke-dashoffset", + "41": "stroke-linecap", + "42": "stroke-linejoin", + "43": "stroke-miterlimit", + "44": "stroke-opacity", + "45": "stroke-width", + "46": "tab-size", + "47": "text-align", + "48": "text-anchor", + "49": "text-decoration-line", + "50": "text-indent", + "51": "text-justify", + "52": "text-shadow", + "53": "text-transform", + "54": "visibility", + "55": "white-space", + "56": "word-break", + "57": "word-spacing", + "58": "word-wrap", + "59": "writing-mode", + "60": "align-content", + "61": "align-items", + "62": "align-self", + "63": "animation-delay", + "64": "animation-direction", + "65": "animation-duration", + "66": "animation-fill-mode", + "67": "animation-iteration-count", + "68": "animation-name", + "69": "animation-play-state", + "70": "animation-timing-function", + "71": "appearance", + "72": "aspect-ratio", + "73": "backdrop-filter", + "74": "background-attachment", + "75": "background-clip", + "76": "background-color", + "77": "background-image", + "78": "background-origin", + "79": "background-position-x", + "80": "background-position-y", + "81": "background-repeat", + "82": "background-size", + "83": "border-bottom-color", + "84": "border-bottom-left-radius", + "85": "border-bottom-right-radius", + "86": "border-bottom-style", + "87": "border-bottom-width", + "88": "border-left-color", + "89": "border-left-style", + "90": "border-left-width", + "91": "border-right-color", + "92": "border-right-style", + "93": "border-right-width", + "94": "border-top-color", + "95": "border-top-left-radius", + "96": "border-top-right-radius", + "97": "border-top-style", + "98": "border-top-width", + "99": "bottom", + "100": "box-shadow", + "101": "box-sizing", + "102": "clear", + "103": "clip", + "104": "clip-path", + "105": "column-count", + "106": "column-gap", + "107": "column-span", + "108": "column-width", + "109": "content", + "110": "content-visibility", + "111": "counter-increment", + "112": "counter-reset", + "113": "counter-set", + "114": "cx", + "115": "cy", + "116": "display", + "117": "filter", + "118": "flex-basis", + "119": "flex-direction", + "120": "flex-grow", + "121": "flex-shrink", + "122": "flex-wrap", + "123": "float", + "124": "grid-auto-columns", + "125": "grid-auto-flow", + "126": "grid-auto-rows", + "127": "grid-column-end", + "128": "grid-column-start", + "129": "grid-row-end", + "130": "grid-row-start", + "131": "grid-template-areas", + "132": "grid-template-columns", + "133": "grid-template-rows", + "134": "height", + "135": "inline-size", + "136": "inset-block-end", + "137": "inset-block-start", + "138": "inset-inline-end", + "139": "inset-inline-start", + "140": "justify-content", + "141": "justify-items", + "142": "justify-self", + "143": "left", + "144": "margin-block-end", + "145": "margin-block-start", + "146": "margin-bottom", + "147": "margin-inline-end", + "148": "margin-inline-start", + "149": "margin-left", + "150": "margin-right", + "151": "margin-top", + "152": "mask", + "153": "mask-image", + "154": "mask-type", + "155": "max-height", + "156": "max-inline-size", + "157": "max-width", + "158": "min-height", + "159": "min-inline-size", + "160": "min-width", + "161": "object-fit", + "162": "object-position", + "163": "opacity", + "164": "order", + "165": "outline-color", + "166": "outline-offset", + "167": "outline-style", + "168": "outline-width", + "169": "overflow-x", + "170": "overflow-y", + "171": "padding-block-end", + "172": "padding-block-start", + "173": "padding-bottom", + "174": "padding-inline-end", + "175": "padding-inline-start", + "176": "padding-left", + "177": "padding-right", + "178": "padding-top", + "179": "position", + "180": "r", + "181": "right", + "182": "rotate", + "183": "row-gap", + "184": "rx", + "185": "ry", + "186": "scale", + "187": "scrollbar-gutter", + "188": "scrollbar-width", + "189": "stop-color", + "190": "stop-opacity", + "191": "table-layout", + "192": "text-decoration-color", + "193": "text-decoration-style", + "194": "text-decoration-thickness", + "195": "text-overflow", + "196": "top", + "197": "transform", + "198": "transform-box", + "199": "transform-origin", + "200": "transition-delay", + "201": "transition-duration", + "202": "transition-property", + "203": "transition-timing-function", + "204": "translate", + "205": "unicode-bidi", + "206": "user-select", + "207": "vertical-align", + "208": "width", + "209": "x", + "210": "y", + "211": "z-index" } All properties associated with document.body.style by default: {} diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index dce8309831166..7fb9417104b60 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -15,7 +15,13 @@ font-feature-settings: normal font-language-override: normal font-size: 16px font-style: normal -font-variant: normal +font-variant-alternates: normal +font-variant-caps: normal +font-variant-east-asian: normal +font-variant-emoji: normal +font-variant-ligatures: normal +font-variant-numeric: normal +font-variant-position: normal font-variation-settings: normal font-weight: 400 font-width: normal @@ -126,7 +132,7 @@ grid-row-start: auto grid-template-areas: none grid-template-columns: auto grid-template-rows: auto -height: 2176px +height: 2278px inline-size: auto inset-block-end: auto inset-block-start: auto From 083f4f3d084043f21956f529429308c4d7bf5856 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 5 Dec 2024 01:21:45 +0100 Subject: [PATCH 234/237] LibWeb: Layout/Shape font-variant-* css properties --- Libraries/LibGfx/Font/ScaledFont.cpp | 6 +- Libraries/LibGfx/TextLayout.cpp | 25 +- Libraries/LibGfx/TextLayout.h | 11 +- .../LibWeb/HTML/CanvasRenderingContext2D.cpp | 2 +- .../LibWeb/Layout/InlineLevelIterator.cpp | 259 +++++++++++++++++- Libraries/LibWeb/Layout/InlineLevelIterator.h | 3 + Libraries/LibWeb/Layout/Node.cpp | 16 +- .../LibWeb/Painting/DisplayListRecorder.cpp | 2 +- 8 files changed, 310 insertions(+), 14 deletions(-) diff --git a/Libraries/LibGfx/Font/ScaledFont.cpp b/Libraries/LibGfx/Font/ScaledFont.cpp index 8569d3fc14c6e..06f34ec04ff7a 100644 --- a/Libraries/LibGfx/Font/ScaledFont.cpp +++ b/Libraries/LibGfx/Font/ScaledFont.cpp @@ -58,13 +58,13 @@ ScaledFontMetrics ScaledFont::metrics() const return metrics; } -float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this); } -float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this); } +float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this, {}); } +float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this, {}); } float ScaledFont::glyph_width(u32 code_point) const { auto string = String::from_code_point(code_point); - return measure_text_width(Utf8View(string), *this); + return measure_text_width(Utf8View(string), *this, {}); } NonnullRefPtr ScaledFont::scaled_with_size(float point_size) const diff --git a/Libraries/LibGfx/TextLayout.cpp b/Libraries/LibGfx/TextLayout.cpp index 75314f2cadcbb..3c6f40e75f7bb 100644 --- a/Libraries/LibGfx/TextLayout.cpp +++ b/Libraries/LibGfx/TextLayout.cpp @@ -12,7 +12,7 @@ namespace Gfx { -RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type) +RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type, ShapeFeatures const& features) { hb_buffer_t* buffer = hb_buffer_create(); ScopeGuard destroy_buffer = [&]() { hb_buffer_destroy(buffer); }; @@ -24,7 +24,22 @@ RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf Vector const input_glyph_info({ glyph_info, glyph_count }); auto* hb_font = font.harfbuzz_font(); - hb_shape(hb_font, buffer, nullptr, 0); + hb_feature_t const* hb_features_data = nullptr; + Vector hb_features; + if (!features.is_empty()) { + hb_features.ensure_capacity(features.size()); + for (auto const& feature : features) { + hb_features.append({ + .tag = HB_TAG(feature.tag[0], feature.tag[1], feature.tag[2], feature.tag[3]), + .value = feature.value, + .start = 0, + .end = HB_FEATURE_GLOBAL_END, + }); + } + hb_features_data = hb_features.data(); + } + + hb_shape(hb_font, buffer, hb_features_data, features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); auto* positions = hb_buffer_get_glyph_positions(buffer, &glyph_count); @@ -45,12 +60,14 @@ RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf point.translate_by(letter_spacing, 0); } + hb_buffer_reset(buffer); + return adopt_ref(*new Gfx::GlyphRun(move(glyph_run), font, text_type, point.x())); } -float measure_text_width(Utf8View const& string, Gfx::Font const& font) +float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features) { - auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common); + auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common, features); return glyph_run->width(); } diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index 9bfc6e7c1587a..0a63885983951 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -26,6 +26,13 @@ struct DrawGlyph { } }; +typedef struct ShapeFeature { + char tag[4]; + u32 value; +} ShapeFeature; + +using ShapeFeatures = Vector; + class GlyphRun : public RefCounted { public: enum class TextType { @@ -60,7 +67,7 @@ class GlyphRun : public RefCounted { float m_width { 0 }; }; -RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType); -float measure_text_width(Utf8View const& string, Gfx::Font const& font); +RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType, ShapeFeatures const& features); +float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features); } diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 4c48a68d5686b..e38fa94b0c548 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -591,7 +591,7 @@ CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(By Gfx::FloatPoint anchor { 0, 0 }; auto physical_alignment = Gfx::TextAlignment::CenterLeft; - auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr); + auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr, {}); // 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point. PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast(glyph_run->width()), static_cast(height) } }; diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 333423e8f7796..8031f21af71f0 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -200,6 +201,261 @@ Gfx::GlyphRun::TextType InlineLevelIterator::resolve_text_direction_from_context return Gfx::GlyphRun::TextType::ContextDependent; } +HashMap InlineLevelIterator::shape_features_map() const +{ + HashMap features; + + auto& computed_values = m_current_node->computed_values(); + + // 6.4 https://drafts.csswg.org/css-fonts/#font-variant-ligatures-prop + auto ligature_or_null = computed_values.font_variant_ligatures(); + if (ligature_or_null.has_value()) { + auto ligature = ligature_or_null.release_value(); + if (ligature.none) { + /* nothing */ + } else { + switch (ligature.common) { + case Gfx::FontVariantLigatures::Common::Common: + // Enables display of common ligatures (OpenType features: liga, clig). + features.set("liga"sv, 1); + features.set("clig"sv, 1); + break; + case Gfx::FontVariantLigatures::Common::NoCommon: + // Disables display of common ligatures (OpenType features: liga, clig). + features.set("liga"sv, 0); + features.set("clig"sv, 0); + break; + case Gfx::FontVariantLigatures::Common::Unset: + break; + } + + switch (ligature.discretionary) { + case Gfx::FontVariantLigatures::Discretionary::Discretionary: + // Enables display of discretionary ligatures (OpenType feature: dlig). + features.set("dlig"sv, 1); + break; + case Gfx::FontVariantLigatures::Discretionary::NoDiscretionary: + // Disables display of discretionary ligatures (OpenType feature: dlig). + features.set("dlig"sv, 0); + break; + case Gfx::FontVariantLigatures::Discretionary::Unset: + break; + } + + switch (ligature.historical) { + case Gfx::FontVariantLigatures::Historical::Historical: + // Enables display of historical ligatures (OpenType feature: hlig). + features.set("hlig"sv, 1); + break; + case Gfx::FontVariantLigatures::Historical::NoHistorical: + // Disables display of historical ligatures (OpenType feature: hlig). + features.set("hlig"sv, 0); + break; + case Gfx::FontVariantLigatures::Historical::Unset: + break; + } + + switch (ligature.contextual) { + case Gfx::FontVariantLigatures::Contextual::Contextual: + // Enables display of contextual ligatures (OpenType feature: calt). + features.set("calt"sv, 1); + break; + case Gfx::FontVariantLigatures::Contextual::NoContextual: + // Disables display of contextual ligatures (OpenType feature: calt). + features.set("calt"sv, 0); + break; + case Gfx::FontVariantLigatures::Contextual::Unset: + break; + } + } + } else { + // A value of normal specifies that common default features are enabled, as described in detail in the next section. + features.set("liga"sv, 1); + features.set("clig"sv, 1); + } + + // 6.5 https://drafts.csswg.org/css-fonts/#font-variant-position-prop + switch (computed_values.font_variant_position()) { + case CSS::FontVariantPosition::Normal: + // None of the features listed below are enabled. + break; + case CSS::FontVariantPosition::Sub: + // Enables display of subscripts (OpenType feature: subs). + features.set("subs"sv, 1); + break; + case CSS::FontVariantPosition::Super: + // Enables display of superscripts (OpenType feature: sups). + features.set("sups"sv, 1); + break; + default: + break; + } + + // 6.6 https://drafts.csswg.org/css-fonts/#font-variant-caps-prop + switch (computed_values.font_variant_caps()) { + case CSS::FontVariantCaps::Normal: + // None of the features listed below are enabled. + break; + case CSS::FontVariantCaps::SmallCaps: + // Enables display of small capitals (OpenType feature: smcp). Small-caps glyphs typically use the form of uppercase letters but are reduced to the size of lowercase letters. + features.set("smcp"sv, 1); + break; + case CSS::FontVariantCaps::AllSmallCaps: + // Enables display of small capitals for both upper and lowercase letters (OpenType features: c2sc, smcp). + features.set("c2sc"sv, 1); + features.set("smcp"sv, 1); + break; + case CSS::FontVariantCaps::PetiteCaps: + // Enables display of petite capitals (OpenType feature: pcap). + features.set("pcap"sv, 1); + break; + case CSS::FontVariantCaps::AllPetiteCaps: + // Enables display of petite capitals for both upper and lowercase letters (OpenType features: c2pc, pcap). + features.set("c2pc"sv, 1); + features.set("pcap"sv, 1); + break; + case CSS::FontVariantCaps::Unicase: + // Enables display of mixture of small capitals for uppercase letters with normal lowercase letters (OpenType feature: unic). + features.set("unic"sv, 1); + break; + case CSS::FontVariantCaps::TitlingCaps: + // Enables display of titling capitals (OpenType feature: titl). + features.set("titl"sv, 1); + break; + default: + break; + } + + // 6.7 https://drafts.csswg.org/css-fonts/#font-variant-numeric-prop + auto numeric_or_null = computed_values.font_variant_numeric(); + if (numeric_or_null.has_value()) { + auto numeric = numeric_or_null.release_value(); + if (numeric.figure == Gfx::FontVariantNumeric::Figure::Oldstyle) { + // Enables display of old-style numerals (OpenType feature: onum). + features.set("onum"sv, 1); + } else if (numeric.figure == Gfx::FontVariantNumeric::Figure::Lining) { + // Enables display of lining numerals (OpenType feature: lnum). + features.set("lnum"sv, 1); + } + + if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Proportional) { + // Enables display of proportional numerals (OpenType feature: pnum). + features.set("pnum"sv, 1); + } else if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Tabular) { + // Enables display of tabular numerals (OpenType feature: tnum). + features.set("tnum"sv, 1); + } + + if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Diagonal) { + // Enables display of diagonal fractions (OpenType feature: frac). + features.set("frac"sv, 1); + } else if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Stacked) { + // Enables display of stacked fractions (OpenType feature: afrc). + features.set("afrc"sv, 1); + features.set("afrc"sv, 1); + } + + if (numeric.ordinal) { + // Enables display of letter forms used with ordinal numbers (OpenType feature: ordn). + features.set("ordn"sv, 1); + } + if (numeric.slashed_zero) { + // Enables display of slashed zeros (OpenType feature: zero). + features.set("zero"sv, 1); + } + } + + // 6.10 https://drafts.csswg.org/css-fonts/#font-variant-east-asian-prop + auto east_asian_or_null = computed_values.font_variant_east_asian(); + if (east_asian_or_null.has_value()) { + auto east_asian = east_asian_or_null.release_value(); + switch (east_asian.variant) { + case Gfx::FontVariantEastAsian::Variant::Jis78: + // Enables display of JIS78 forms (OpenType feature: jp78). + features.set("jp78"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis83: + // Enables display of JIS83 forms (OpenType feature: jp83). + features.set("jp83"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis90: + // Enables display of JIS90 forms (OpenType feature: jp90). + features.set("jp90"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis04: + // Enables display of JIS04 forms (OpenType feature: jp04). + features.set("jp04"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Simplified: + // Enables display of simplified forms (OpenType feature: smpl). + features.set("smpl"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Traditional: + // Enables display of traditional forms (OpenType feature: trad). + features.set("trad"sv, 1); + break; + default: + break; + } + switch (east_asian.width) { + case Gfx::FontVariantEastAsian::Width::FullWidth: + // Enables display of full-width forms (OpenType feature: fwid). + features.set("fwid"sv, 1); + break; + case Gfx::FontVariantEastAsian::Width::Proportional: + // Enables display of proportional-width forms (OpenType feature: pwid). + features.set("pwid"sv, 1); + break; + default: + break; + } + if (east_asian.ruby) { + // Enables display of ruby forms (OpenType feature: ruby). + features.set("ruby"sv, 1); + } + } + + return features; +} + +Gfx::ShapeFeatures InlineLevelIterator::create_and_merge_font_features() const +{ + HashMap merged_features; + auto& computed_values = m_inline_formatting_context.containing_block().computed_values(); + + // https://www.w3.org/TR/css-fonts-3/#feature-precedence + + // FIXME 1. Font features enabled by default, including features required for a given script. + + // FIXME 2. If the font is defined via an @font-face rule, the font features implied by the font-feature-settings descriptor in the @font-face rule. + + // 3. Font features implied by the value of the ‘font-variant’ property, the related ‘font-variant’ subproperties and any other CSS property that uses OpenType features (e.g. the ‘font-kerning’ property). + for (auto& it : shape_features_map()) { + merged_features.set(it.key, it.value); + } + + // FIXME 4. Feature settings determined by properties other than ‘font-variant’ or ‘font-feature-settings’. For example, setting a non-default value for the ‘letter-spacing’ property disables common ligatures. + + // 5. Font features implied by the value of ‘font-feature-settings’ property. + auto resolution_context = CSS::Length::ResolutionContext::for_layout_node(*m_current_node.ptr()); + auto font_feature_settings = computed_values.font_feature_settings(); + if (font_feature_settings.has_value()) { + auto const& feature_settings = font_feature_settings.value(); + for (auto const& [key, feature_value] : feature_settings) { + merged_features.set(key, feature_value.resolved(resolution_context)); + } + } + + Gfx::ShapeFeatures shape_features; + shape_features.ensure_capacity(merged_features.size()); + + for (auto& it : merged_features) { + shape_features.append({ { it.key[0], it.key[1], it.key[2], it.key[3] }, static_cast(it.value) }); + } + + return shape_features; +} + Optional InlineLevelIterator::next_without_lookahead() { if (!m_current_node) @@ -293,7 +549,8 @@ Optional InlineLevelIterator::next_without_lookahead( x = tab_stop_dist.to_float(); } - auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type); + auto shape_features = create_and_merge_font_features(); + auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type, shape_features); CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run->width()); diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Libraries/LibWeb/Layout/InlineLevelIterator.h index de345215932a2..cdd7a89a0e3b3 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -68,6 +68,9 @@ class InlineLevelIterator { void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics); + HashMap shape_features_map() const; + Gfx::ShapeFeatures create_and_merge_font_features() const; + Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within); Layout::InlineFormattingContext& m_inline_formatting_context; diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index a92d2329466ab..e5c2c30e21475 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -473,12 +473,24 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto box_sizing = computed_style.box_sizing(); box_sizing.has_value()) computed_values.set_box_sizing(box_sizing.release_value()); - if (auto maybe_font_variant = computed_style.font_variant(); maybe_font_variant.has_value()) - computed_values.set_font_variant(maybe_font_variant.release_value()); if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) computed_values.set_font_language_override(maybe_font_language_override.release_value()); if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value()) computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value()); + if (auto maybe_font_variant_alternates = computed_style.font_variant_alternates(); maybe_font_variant_alternates.has_value()) + computed_values.set_font_variant_alternates(maybe_font_variant_alternates.release_value()); + if (auto maybe_font_variant_caps = computed_style.font_variant_caps(); maybe_font_variant_caps.has_value()) + computed_values.set_font_variant_caps(maybe_font_variant_caps.release_value()); + if (auto maybe_font_variant_east_asian = computed_style.font_variant_east_asian(); maybe_font_variant_east_asian.has_value()) + computed_values.set_font_variant_east_asian(maybe_font_variant_east_asian.release_value()); + if (auto maybe_font_variant_emoji = computed_style.font_variant_emoji(); maybe_font_variant_emoji.has_value()) + computed_values.set_font_variant_emoji(maybe_font_variant_emoji.release_value()); + if (auto maybe_font_variant_ligatures = computed_style.font_variant_ligatures(); maybe_font_variant_ligatures.has_value()) + computed_values.set_font_variant_ligatures(maybe_font_variant_ligatures.release_value()); + if (auto maybe_font_variant_numeric = computed_style.font_variant_numeric(); maybe_font_variant_numeric.has_value()) + computed_values.set_font_variant_numeric(maybe_font_variant_numeric.release_value()); + if (auto maybe_font_variant_position = computed_style.font_variant_position(); maybe_font_variant_position.has_value()) + computed_values.set_font_variant_position(maybe_font_variant_position.release_value()); if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) computed_values.set_font_variation_settings(maybe_font_variation_settings.release_value()); diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 7a9776546cdd1..a02b88ecc7185 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -230,7 +230,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G if (rect.is_empty()) return; - auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr); + auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr, {}); float baseline_x = 0; if (alignment == Gfx::TextAlignment::CenterLeft) { baseline_x = rect.x(); From 6fd7b9b6b98f4aadff43c350d82bd7e52e366b6a Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 5 Dec 2024 01:18:54 +0100 Subject: [PATCH 235/237] LibWeb: Render otf fonts This is required by WPT tests so we can validate the OpenType/features are properly implemented. --- Libraries/LibWeb/CSS/StyleComputer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 8b1ad246ca911..51db1789c4fab 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -260,7 +260,7 @@ ErrorOr> FontLoader::try_load_font() mime_type = MimeSniff::Resource::sniff(resource()->encoded_data(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font }); } if (mime_type.has_value()) { - if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv) { + if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv || mime_type->essence() == "font/otf"sv) { if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { return result; } From 15a96e841b0f8bde567d5fbdc03603886578f1b7 Mon Sep 17 00:00:00 2001 From: stasoid Date: Wed, 18 Dec 2024 06:36:28 +0500 Subject: [PATCH 236/237] Meta: Make pthread and mman available for all libraries on Windows by default --- Libraries/LibCore/CMakeLists.txt | 10 ---------- Libraries/LibGC/CMakeLists.txt | 6 ------ Libraries/LibRequests/CMakeLists.txt | 5 ----- Libraries/LibTLS/CMakeLists.txt | 5 ----- Libraries/LibThreading/CMakeLists.txt | 2 -- Meta/Lagom/CMakeLists.txt | 10 ++++++++++ 6 files changed, 10 insertions(+), 28 deletions(-) diff --git a/Libraries/LibCore/CMakeLists.txt b/Libraries/LibCore/CMakeLists.txt index 6b9427d3d0f63..c54c02166c316 100644 --- a/Libraries/LibCore/CMakeLists.txt +++ b/Libraries/LibCore/CMakeLists.txt @@ -22,10 +22,6 @@ serenity_lib(LibCoreMinimal coreminimal) if (WIN32) find_path(DIRENT_INCLUDE_DIR dirent.h REQUIRED) target_include_directories(LibCoreMinimal PRIVATE ${DIRENT_INCLUDE_DIR}) - - find_package(mman REQUIRED) - target_include_directories(LibCoreMinimal PRIVATE ${MMAN_INCLUDE_DIR}) - target_link_libraries(LibCoreMinimal PRIVATE ${MMAN_LIBRARY}) endif() if (LAGOM_TOOLS_ONLY) @@ -116,11 +112,5 @@ if (ANDROID) endif() if (WIN32) - target_include_directories(LibCore PRIVATE ${MMAN_INCLUDE_DIR}) - - find_package(pthread REQUIRED) - target_include_directories(LibCore PRIVATE ${PTHREAD_INCLUDE_DIR}) - target_link_libraries(LibCore PRIVATE ${PTHREAD_LIBRARY}) - target_link_libraries(LibCore PRIVATE ws2_32.lib) endif() diff --git a/Libraries/LibGC/CMakeLists.txt b/Libraries/LibGC/CMakeLists.txt index 805c7461146a0..7d93203ab7037 100644 --- a/Libraries/LibGC/CMakeLists.txt +++ b/Libraries/LibGC/CMakeLists.txt @@ -22,9 +22,3 @@ if (ENABLE_SWIFT) target_link_libraries(LibGC PRIVATE AK) add_swift_target_properties(LibGC LAGOM_LIBRARIES AK) endif() - -if (WIN32) - find_package(mman REQUIRED) - target_include_directories(LibGC PRIVATE ${MMAN_INCLUDE_DIR}) - target_link_libraries(LibGC PRIVATE ${MMAN_LIBRARY}) -endif() diff --git a/Libraries/LibRequests/CMakeLists.txt b/Libraries/LibRequests/CMakeLists.txt index 835b0c233c8ff..65f71ca42309e 100644 --- a/Libraries/LibRequests/CMakeLists.txt +++ b/Libraries/LibRequests/CMakeLists.txt @@ -12,8 +12,3 @@ set(GENERATED_SOURCES serenity_lib(LibRequests requests) target_link_libraries(LibRequests PRIVATE LibCore LibIPC) - -if (WIN32) - find_package(pthread REQUIRED) - target_include_directories(LibRequests PRIVATE ${PTHREAD_INCLUDE_DIR}) -endif() diff --git a/Libraries/LibTLS/CMakeLists.txt b/Libraries/LibTLS/CMakeLists.txt index c9883dc92dcd2..f21dc89a92273 100644 --- a/Libraries/LibTLS/CMakeLists.txt +++ b/Libraries/LibTLS/CMakeLists.txt @@ -14,8 +14,3 @@ serenity_lib(LibTLS tls) target_link_libraries(LibTLS PRIVATE LibCore LibCrypto LibFileSystem) include(ca_certificates_data) - -if (WIN32) - find_package(pthread REQUIRED) - target_include_directories(LibTLS PRIVATE ${PTHREAD_INCLUDE_DIR}) -endif() diff --git a/Libraries/LibThreading/CMakeLists.txt b/Libraries/LibThreading/CMakeLists.txt index 6b3307687c1a2..2e9b204089c3d 100644 --- a/Libraries/LibThreading/CMakeLists.txt +++ b/Libraries/LibThreading/CMakeLists.txt @@ -7,7 +7,5 @@ serenity_lib(LibThreading threading) target_link_libraries(LibThreading PRIVATE LibCore) if (WIN32) - find_package(pthread REQUIRED) - target_include_directories(LibThreading PRIVATE ${PTHREAD_INCLUDE_DIR}) target_link_libraries(LibThreading PUBLIC ${PTHREAD_LIBRARY}) endif() diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index a156a3e07faca..4085e934f5cc1 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -228,6 +228,16 @@ function(lagom_lib target_name fs_name) target_link_libraries(${target_name} PRIVATE AK) endif() + if (WIN32) + find_package(pthread REQUIRED) + target_include_directories(${target_name} PRIVATE ${PTHREAD_INCLUDE_DIR}) + target_link_libraries(${target_name} PRIVATE ${PTHREAD_LIBRARY}) + + find_package(mman REQUIRED) + target_include_directories(${target_name} PRIVATE ${MMAN_INCLUDE_DIR}) + target_link_libraries(${target_name} PRIVATE ${MMAN_LIBRARY}) + endif() + # FIXME: Clean these up so that we don't need so many include dirs if (ENABLE_INSTALL_HEADERS) target_include_directories(${target_name} INTERFACE From 1b033355f62a853fe303da6a99d68fd722564392 Mon Sep 17 00:00:00 2001 From: stasoid Date: Sun, 1 Dec 2024 14:43:10 +0500 Subject: [PATCH 237/237] AK: Simplify usage of windows.h and winsock2.h Use instead of windows.h/winsock2.h to avoid timeval-related errors. Note: winsock2.h includes windows.h --- AK/Time.cpp | 4 +--- AK/Windows.h | 20 +++++++++++++++++++ Libraries/LibCore/CMakeLists.txt | 4 ---- .../EventLoopImplementationWindows.cpp | 4 ++-- 4 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 AK/Windows.h diff --git a/AK/Time.cpp b/AK/Time.cpp index 923f6404da6a2..d10340e5978ae 100644 --- a/AK/Time.cpp +++ b/AK/Time.cpp @@ -8,9 +8,7 @@ #include #ifdef AK_OS_WINDOWS -# define timeval dummy_timeval -# include -# undef timeval +# include #endif namespace AK { diff --git a/AK/Windows.h b/AK/Windows.h new file mode 100644 index 0000000000000..54d0a79afb3b2 --- /dev/null +++ b/AK/Windows.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// This header should be included only in cpp files. +// It should be included after all other files and should be separated from them by a non-#include line to prevent clang-format from changing header order. + +#pragma once + +#include + +#ifdef AK_OS_WINDOWS // needed for Swift +# define timeval dummy_timeval +# include +# undef timeval +# pragma comment(lib, "ws2_32.lib") +# include +#endif diff --git a/Libraries/LibCore/CMakeLists.txt b/Libraries/LibCore/CMakeLists.txt index c54c02166c316..c4b90d12b67d4 100644 --- a/Libraries/LibCore/CMakeLists.txt +++ b/Libraries/LibCore/CMakeLists.txt @@ -110,7 +110,3 @@ endif() if (ANDROID) target_link_libraries(LibCore PRIVATE log) endif() - -if (WIN32) - target_link_libraries(LibCore PRIVATE ws2_32.lib) -endif() diff --git a/Libraries/LibCore/EventLoopImplementationWindows.cpp b/Libraries/LibCore/EventLoopImplementationWindows.cpp index 5794be324079e..6ab3be4690a02 100644 --- a/Libraries/LibCore/EventLoopImplementationWindows.cpp +++ b/Libraries/LibCore/EventLoopImplementationWindows.cpp @@ -8,8 +8,8 @@ #include #include #include -#include -#include + +#include struct Handle { HANDLE handle = NULL;