diff --git a/Tests/LibWeb/Text/expected/DOM/shadow-root-boundary-of-inserted-node-is-traversed.txt b/Tests/LibWeb/Text/expected/DOM/shadow-root-boundary-of-inserted-node-is-traversed.txt
new file mode 100644
index 00000000000000..2bfbd60d9c4dd4
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/DOM/shadow-root-boundary-of-inserted-node-is-traversed.txt
@@ -0,0 +1 @@
+Hello from script in the shadow root of the just inserted div!
diff --git a/Tests/LibWeb/Text/expected/HTML/iframe-successfully-loads-in-shadow-root.txt b/Tests/LibWeb/Text/expected/HTML/iframe-successfully-loads-in-shadow-root.txt
new file mode 100644
index 00000000000000..e3c171e4a7e464
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/iframe-successfully-loads-in-shadow-root.txt
@@ -0,0 +1,2 @@
+Received a message: 'Hello from iframe in the shadow root of the just inserted div!'
+Was it from the shadow root iframe? true
diff --git a/Tests/LibWeb/Text/input/DOM/shadow-root-boundary-of-inserted-node-is-traversed.html b/Tests/LibWeb/Text/input/DOM/shadow-root-boundary-of-inserted-node-is-traversed.html
new file mode 100644
index 00000000000000..b545db3d620b73
--- /dev/null
+++ b/Tests/LibWeb/Text/input/DOM/shadow-root-boundary-of-inserted-node-is-traversed.html
@@ -0,0 +1,14 @@
+
+
+
diff --git a/Tests/LibWeb/Text/input/HTML/iframe-successfully-loads-in-shadow-root.html b/Tests/LibWeb/Text/input/HTML/iframe-successfully-loads-in-shadow-root.html
new file mode 100644
index 00000000000000..21ba827ee83b4d
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/iframe-successfully-loads-in-shadow-root.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp b/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp
index cb8b9de37df90c..b83597611c847e 100644
--- a/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp
+++ b/Userland/Libraries/LibWeb/CSS/MediaQueryList.cpp
@@ -73,7 +73,7 @@ bool MediaQueryList::evaluate()
}
// https://www.w3.org/TR/cssom-view/#dom-mediaquerylist-addlistener
-void MediaQueryList::add_listener(DOM::IDLEventListener* listener)
+void MediaQueryList::add_listener(JS::GCPtr listener)
{
// 1. If listener is null, terminate these steps.
if (!listener)
@@ -87,12 +87,13 @@ void MediaQueryList::add_listener(DOM::IDLEventListener* listener)
}
// https://www.w3.org/TR/cssom-view/#dom-mediaquerylist-removelistener
-void MediaQueryList::remove_listener(DOM::IDLEventListener* listener)
+void MediaQueryList::remove_listener(JS::GCPtr listener)
{
// 1. Remove an event listener from the associated list of event listeners, whose type is change, callback is listener, and capture is false.
// NOTE: While the spec doesn't technically use remove_event_listener and instead manipulates the list directly, every major engine uses remove_event_listener.
// This means if an event listener removes another event listener that comes after it, the removed event listener will not be invoked.
- remove_event_listener_without_options(HTML::EventNames::change, *listener);
+ if (listener)
+ remove_event_listener_without_options(HTML::EventNames::change, *listener);
}
void MediaQueryList::set_onchange(WebIDL::CallbackType* event_handler)
diff --git a/Userland/Libraries/LibWeb/CSS/MediaQueryList.h b/Userland/Libraries/LibWeb/CSS/MediaQueryList.h
index 728f9e7bf63c01..1a6832612d6ec9 100644
--- a/Userland/Libraries/LibWeb/CSS/MediaQueryList.h
+++ b/Userland/Libraries/LibWeb/CSS/MediaQueryList.h
@@ -26,8 +26,8 @@ class MediaQueryList final : public DOM::EventTarget {
bool matches() const;
bool evaluate();
- void add_listener(DOM::IDLEventListener*);
- void remove_listener(DOM::IDLEventListener*);
+ void add_listener(JS::GCPtr);
+ void remove_listener(JS::GCPtr);
void set_onchange(WebIDL::CallbackType*);
WebIDL::CallbackType* onchange();
diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
index c76c3b28878a1e..d0a1c01350d913 100644
--- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
+++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
@@ -97,21 +97,37 @@ class ShadowRoot final : public DocumentFragment {
template<>
inline bool Node::fast_is() const { return node_type() == to_underlying(NodeType::DOCUMENT_FRAGMENT_NODE) && is_shadow_root(); }
+// https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
+// In shadow-including tree order is shadow-including preorder, depth-first traversal of a node tree.
+// Shadow-including preorder, depth-first traversal of a node tree tree is preorder, depth-first traversal
+// of tree, with for each shadow host encountered in tree, shadow-including preorder, depth-first traversal
+// of that element’s shadow root’s node tree just after it is encountered.
+
+// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
+// An object A is a shadow-including descendant of an object B, if A is a descendant of B, or A’s root is a
+// shadow root and A’s root’s host is a shadow-including inclusive descendant of B.
+
+// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
+// A shadow-including inclusive descendant is an object or one of its shadow-including descendants.
+
template
inline TraversalDecision Node::for_each_shadow_including_inclusive_descendant(Callback callback)
{
if (callback(*this) == TraversalDecision::Break)
return TraversalDecision::Break;
- for (auto* child = first_child(); child; child = child->next_sibling()) {
- if (child->is_element()) {
- if (auto shadow_root = static_cast(child)->shadow_root()) {
- if (shadow_root->for_each_shadow_including_inclusive_descendant(callback) == TraversalDecision::Break)
- return TraversalDecision::Break;
- }
+
+ if (is_element()) {
+ if (auto shadow_root = static_cast(this)->shadow_root()) {
+ if (shadow_root->for_each_shadow_including_inclusive_descendant(callback) == TraversalDecision::Break)
+ return TraversalDecision::Break;
}
+ }
+
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
if (child->for_each_shadow_including_inclusive_descendant(callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
+
return TraversalDecision::Continue;
}
@@ -119,12 +135,6 @@ template
inline TraversalDecision Node::for_each_shadow_including_descendant(Callback callback)
{
for (auto* child = first_child(); child; child = child->next_sibling()) {
- if (child->is_element()) {
- if (JS::GCPtr shadow_root = static_cast(child)->shadow_root()) {
- if (shadow_root->for_each_shadow_including_inclusive_descendant(callback) == TraversalDecision::Break)
- return TraversalDecision::Break;
- }
- }
if (child->for_each_shadow_including_inclusive_descendant(callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
index da0cdad6379dcb..1cf23a8e5b025d 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp
@@ -58,17 +58,26 @@ void HTMLIFrameElement::inserted()
{
HTMLElement::inserted();
- // When an iframe element element is inserted into a document whose browsing context is non-null, the user agent must run these steps:
- if (in_a_document_tree() && document().browsing_context() && document().is_fully_active()) {
- // 1. Create a new child navigable for element.
- MUST(create_new_child_navigable(JS::create_heap_function(realm().heap(), [this] {
- // 3. Process the iframe attributes for element, with initialInsertion set to true.
- process_the_iframe_attributes(true);
- set_content_navigable_initialized();
- })));
-
- // FIXME: 2. If element has a sandbox attribute, then parse the sandboxing directive given the attribute's value and element's iframe sandboxing flag set.
- }
+ // The iframe HTML element insertion steps, given insertedNode, are:
+ // 1. If insertedNode's shadow-including root's browsing context is null, then return.
+ if (!is(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.
+ MUST(create_new_child_navigable(JS::create_heap_function(realm().heap(), [this] {
+ // FIXME: 3. 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.
+ process_the_iframe_attributes(true);
+ set_content_navigable_initialized();
+ })));
}
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes
diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp b/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp
index 57c23b0e921f0c..80fd4011f0f9a1 100644
--- a/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp
+++ b/Userland/Libraries/LibWeb/WebAudio/AudioContext.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -92,7 +93,6 @@ void AudioContext::initialize(JS::Realm& realm)
void AudioContext::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
- visitor.visit(m_pending_promises);
visitor.visit(m_pending_resume_promises);
}
@@ -107,7 +107,6 @@ AudioTimestamp AudioContext::get_output_timestamp()
WebIDL::ExceptionOr> AudioContext::resume()
{
auto& realm = this->realm();
- auto& vm = realm.vm();
// 1. If this's relevant global object's associated Document is not fully active then return a promise rejected with "InvalidStateError" DOMException.
auto const& associated_document = verify_cast(HTML::relevant_global_object(*this)).associated_document();
@@ -128,8 +127,8 @@ WebIDL::ExceptionOr> AudioContext::resume()
// 5. If the context is not allowed to start, append promise to [[pending promises]] and [[pending resume promises]] and abort these steps, returning promise.
if (m_allowed_to_start) {
- TRY_OR_THROW_OOM(vm, m_pending_promises.try_append(promise));
- TRY_OR_THROW_OOM(vm, m_pending_resume_promises.try_append(promise));
+ m_pending_promises.append(promise);
+ m_pending_resume_promises.append(promise);
}
// 6. Set the [[control thread state]] on the AudioContext to running.
@@ -147,28 +146,36 @@ WebIDL::ExceptionOr> AudioContext::resume()
if (!start_rendering_audio_graph()) {
// 7.4: In case of failure, queue a media element task to execute the following steps:
queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, this]() {
+ HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+
// 7.4.1: Reject all promises from [[pending resume promises]] in order, then clear [[pending resume promises]].
for (auto const& promise : m_pending_resume_promises) {
WebIDL::reject_promise(realm, promise, JS::js_null());
+
+ // 7.4.2: Additionally, remove those promises from [[pending promises]].
+ m_pending_promises.remove_first_matching([&promise](auto& pending_promise) {
+ return pending_promise == promise;
+ });
}
m_pending_resume_promises.clear();
-
- // FIXME: 7.4.2: Additionally, remove those promises from [[pending promises]].
}));
}
// 7.5: queue a media element task to execute the following steps:
queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, promise, this]() {
+ HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+
// 7.5.1: Resolve all promises from [[pending resume promises]] in order.
+ // 7.5.2: Clear [[pending resume promises]]. Additionally, remove those promises from
+ // [[pending promises]].
for (auto const& pending_resume_promise : m_pending_resume_promises) {
*pending_resume_promise->resolve();
+ m_pending_promises.remove_first_matching([&pending_resume_promise](auto& pending_promise) {
+ return pending_promise == pending_resume_promise;
+ });
}
-
- // 7.5.2: Clear [[pending resume promises]].
m_pending_resume_promises.clear();
- // FIXME: Additionally, remove those promises from [[pending promises]].
-
// 7.5.3: Resolve promise.
*promise->resolve();
@@ -192,7 +199,6 @@ WebIDL::ExceptionOr> AudioContext::resume()
WebIDL::ExceptionOr> AudioContext::suspend()
{
auto& realm = this->realm();
- auto& vm = realm.vm();
// 1. If this's relevant global object's associated Document is not fully active then return a promise rejected with "InvalidStateError" DOMException.
auto const& associated_document = verify_cast(HTML::relevant_global_object(*this)).associated_document();
@@ -209,7 +215,7 @@ WebIDL::ExceptionOr> AudioContext::suspend()
}
// 4. Append promise to [[pending promises]].
- TRY_OR_THROW_OOM(vm, m_pending_promises.try_append(promise));
+ m_pending_promises.append(promise);
// 5. Set [[suspended by user]] to true.
m_suspended_by_user = true;
@@ -227,6 +233,8 @@ WebIDL::ExceptionOr> AudioContext::suspend()
// 7.3: queue a media element task to execute the following steps:
queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, promise, this]() {
+ HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+
// 7.3.1: Resolve promise.
*promise->resolve();
@@ -280,6 +288,8 @@ WebIDL::ExceptionOr> AudioContext::close()
// 5.4: queue a media element task to execute the following steps:
queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, promise, this]() {
+ HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+
// 5.4.1: Resolve promise.
*promise->resolve();
diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioContext.h b/Userland/Libraries/LibWeb/WebAudio/AudioContext.h
index a1155f567813c9..daad254029c034 100644
--- a/Userland/Libraries/LibWeb/WebAudio/AudioContext.h
+++ b/Userland/Libraries/LibWeb/WebAudio/AudioContext.h
@@ -49,7 +49,6 @@ class AudioContext final : public BaseAudioContext {
double m_output_latency { 0 };
bool m_allowed_to_start = true;
- Vector> m_pending_promises;
Vector> m_pending_resume_promises;
bool m_suspended_by_user = false;
diff --git a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp
index df7ca9064ca92b..7839b56d3b0f9f 100644
--- a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp
+++ b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp
@@ -43,6 +43,7 @@ void BaseAudioContext::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_destination);
+ visitor.visit(m_pending_promises);
}
void BaseAudioContext::set_onstatechange(WebIDL::CallbackType* event_handler)
@@ -144,7 +145,8 @@ JS::NonnullGCPtr BaseAudioContext::decode_audio_data(JS::Handle BaseAudioContext::decode_audio_data(JS::Handle m_destination;
+ Vector> m_pending_promises;
private:
void queue_a_decoding_operation(JS::NonnullGCPtr, JS::Handle, JS::GCPtr, JS::GCPtr);