mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-20 15:09:42 +00:00
This also fixes a bug in the view transitions code that was required to get the imported test to pass. The code for setting the initial containing block size just did not set the right thing, since doing so would trigger an error later on. That later error resulted from walking up the tree, without considering that the document element has a parent that is not itself an element. (and then doing element things to it)
1050 lines
52 KiB
C++
1050 lines
52 KiB
C++
/*
|
||
* Copyright (c) 2025, Psychpsyo <psychpsyo@gmail.com>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <LibJS/Runtime/Realm.h>
|
||
#include <LibWeb/CSS/CSSKeyframesRule.h>
|
||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||
#include <LibWeb/HTML/Window.h>
|
||
#include <LibWeb/Layout/Node.h>
|
||
#include <LibWeb/Painting/PaintableBox.h>
|
||
#include <LibWeb/ViewTransition/ViewTransition.h>
|
||
#include <LibWeb/WebIDL/AbstractOperations.h>
|
||
#include <LibWeb/WebIDL/Promise.h>
|
||
|
||
namespace Web::ViewTransition {
|
||
|
||
GC_DEFINE_ALLOCATOR(NamedViewTransitionPseudoElement);
|
||
GC_DEFINE_ALLOCATOR(ReplacedNamedViewTransitionPseudoElement);
|
||
GC_DEFINE_ALLOCATOR(CapturedElement);
|
||
GC_DEFINE_ALLOCATOR(ViewTransition);
|
||
|
||
NamedViewTransitionPseudoElement::NamedViewTransitionPseudoElement(CSS::PseudoElement type, FlyString view_transition_name)
|
||
: m_type(type)
|
||
, m_view_transition_name(view_transition_name)
|
||
{
|
||
}
|
||
|
||
ReplacedNamedViewTransitionPseudoElement::ReplacedNamedViewTransitionPseudoElement(CSS::PseudoElement type, FlyString view_transition_name, RefPtr<Gfx::ImmutableBitmap> content = {})
|
||
: NamedViewTransitionPseudoElement(type, view_transition_name)
|
||
{
|
||
m_content = content;
|
||
}
|
||
|
||
GC::Ref<ViewTransition> ViewTransition::create(JS::Realm& realm)
|
||
{
|
||
auto const& finished_promise = WebIDL::create_promise(realm);
|
||
WebIDL::mark_promise_as_handled(finished_promise);
|
||
return realm.create<ViewTransition>(realm, WebIDL::create_promise(realm), WebIDL::create_promise(realm), finished_promise);
|
||
}
|
||
|
||
ViewTransition::ViewTransition(JS::Realm& realm, GC::Ref<WebIDL::Promise> ready_promise, GC::Ref<WebIDL::Promise> update_callback_done_promise, GC::Ref<WebIDL::Promise> finished_promise)
|
||
: PlatformObject(realm)
|
||
, m_ready_promise(ready_promise)
|
||
, m_update_callback_done_promise(update_callback_done_promise)
|
||
, m_finished_promise(finished_promise)
|
||
, m_transition_root_pseudo_element(heap().allocate<DOM::PseudoElementTreeNode>())
|
||
|
||
{
|
||
}
|
||
|
||
void ViewTransition::initialize(JS::Realm& realm)
|
||
{
|
||
Base::initialize(realm);
|
||
WEB_SET_PROTOTYPE_FOR_INTERFACE(ViewTransition);
|
||
}
|
||
|
||
void ViewTransition::visit_edges(Cell::Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
|
||
for (auto captured_element : m_named_elements) {
|
||
visitor.visit(captured_element.value);
|
||
}
|
||
visitor.visit(m_update_callback);
|
||
visitor.visit(m_ready_promise);
|
||
visitor.visit(m_update_callback_done_promise);
|
||
visitor.visit(m_finished_promise);
|
||
visitor.visit(m_transition_root_pseudo_element);
|
||
}
|
||
|
||
void CapturedElement::visit_edges(Cell::Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
|
||
visitor.visit(new_element);
|
||
|
||
visitor.visit(group_keyframes);
|
||
visitor.visit(group_animation_name_rule);
|
||
visitor.visit(group_styles_rule);
|
||
visitor.visit(image_pair_isolation_rule);
|
||
visitor.visit(image_animation_name_rule);
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#dom-viewtransition-skiptransition
|
||
void ViewTransition::skip_transition()
|
||
{
|
||
// The method steps for skipTransition() are:
|
||
|
||
// 1. If this's phase is not "done", then skip the view transition for this with an "AbortError" DOMException.
|
||
if (m_phase != Phase::Done) {
|
||
skip_the_view_transition(WebIDL::AbortError::create(realm(), "ViewTransition.skip_transition() was called"_utf16));
|
||
}
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#setup-view-transition
|
||
void ViewTransition::setup_view_transition()
|
||
{
|
||
auto& realm = this->realm();
|
||
// To setup view transition for a ViewTransition transition, perform the following steps:
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Flush the update callback queue.
|
||
// AD-HOC: Spec doesn't say what document to flush it for.
|
||
// Lets just use the one we have.
|
||
// (see https://github.com/w3c/csswg-drafts/issues/11986 )
|
||
document.flush_the_update_callback_queue();
|
||
|
||
// 3. Capture the old state for transition.
|
||
auto result = capture_the_old_state();
|
||
// If failure is returned,
|
||
if (result.is_error()) {
|
||
// then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm,
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Failed to capture old state"_utf16));
|
||
// and return.
|
||
return;
|
||
}
|
||
|
||
// 4. Set document’s rendering suppression for view transitions to true.
|
||
document.set_rendering_suppression_for_view_transitions(true);
|
||
|
||
// 5. Queue a global task on the DOM manipulation task source, given transition’s relevant global object, to
|
||
// perform the following steps:
|
||
HTML::queue_global_task(HTML::Task::Source::DOMManipulation, HTML::relevant_global_object(*this), GC::create_function(realm.heap(), [&] {
|
||
HTML::TemporaryExecutionContext context(realm);
|
||
// 1. If transition’s phase is "done", then abort these steps.
|
||
if (m_phase == Phase::Done)
|
||
return;
|
||
|
||
// 2. schedule the update callback for transition.
|
||
schedule_the_update_callback();
|
||
|
||
// 3. Flush the update callback queue.
|
||
// AD-HOC: Spec doesn't say what document to flush it for.
|
||
// Lets just use the one we have.
|
||
// (see https://github.com/w3c/csswg-drafts/issues/11986 )
|
||
// Also, scheduling the update callback should already do this, see https://github.com/w3c/csswg-drafts/issues/11987
|
||
document.flush_the_update_callback_queue();
|
||
}));
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#activate-view-transition
|
||
void ViewTransition::activate_view_transition()
|
||
{
|
||
auto& realm = this->realm();
|
||
// To activate view transition for a ViewTransition transition, perform the following steps:
|
||
|
||
// 1. If transition’s phase is "done", then return.
|
||
// NOTE: This happens if transition was skipped before this point.
|
||
if (m_phase == Phase::Done)
|
||
return;
|
||
|
||
// 2. Set transition’s relevant global object’s associated document’s rendering suppression for view transitions to
|
||
// false.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
document.set_rendering_suppression_for_view_transitions(false);
|
||
|
||
// 3. If transition’s initial snapshot containing block size is not equal to the snapshot containing block size, then
|
||
// skip transition with an "InvalidStateError" DOMException in transition’s relevant Realm, and return.
|
||
auto snapshot_containing_block_size = document.navigable()->snapshot_containing_block_size();
|
||
if (m_initial_snapshot_containing_block_size != snapshot_containing_block_size) {
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Transition's initial snapshot containing block size is not equal to the snapshot containing block size"_utf16));
|
||
return;
|
||
}
|
||
|
||
// 4. Capture the new state for transition.
|
||
auto result = capture_the_new_state();
|
||
// If failure is returned,
|
||
if (result.is_error()) {
|
||
// then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm,
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Failed to capture new state"_utf16));
|
||
// and return.
|
||
return;
|
||
}
|
||
|
||
// 5. For each capturedElement of transition’s named elements' values:
|
||
for (auto captured_element : m_named_elements) {
|
||
// 1. If capturedElement’s new element is not null, then set capturedElement’s new element’s captured in a
|
||
// view transition to true.
|
||
if (captured_element.value->new_element) {
|
||
captured_element.value->new_element->set_captured_in_a_view_transition(true);
|
||
}
|
||
}
|
||
|
||
// 6. Setup transition pseudo-elements for transition.
|
||
setup_transition_pseudo_elements();
|
||
|
||
// 7. Update pseudo-element styles for transition.
|
||
result = update_pseudo_element_styles();
|
||
// If failure is returned,
|
||
if (result.is_error()) {
|
||
// then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm,
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Failed to update pseudo-element styles"_utf16));
|
||
// and return.
|
||
return;
|
||
}
|
||
// NOTE: The above steps will require running document lifecycle phases, to compute information
|
||
// calculated during style/layout.
|
||
// FIXME: Figure out what this entails.
|
||
|
||
// 8. Set transition’s phase to "animating".
|
||
m_phase = Phase::Animating;
|
||
|
||
// 9. Resolve transition’s ready promise.
|
||
WebIDL::resolve_promise(realm, m_ready_promise);
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#capture-the-old-state
|
||
ErrorOr<void> ViewTransition::capture_the_old_state()
|
||
{
|
||
// To capture the old state for ViewTransition transition:
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Let namedElements be transition’s named elements.
|
||
auto& named_elements = m_named_elements;
|
||
|
||
// 3. Let usedTransitionNames be a new set of strings.
|
||
auto used_transition_names = AK::OrderedHashTable<FlyString>();
|
||
|
||
// 4. Let captureElements be a new list of elements.
|
||
auto capture_elements = AK::Vector<DOM::Element&>();
|
||
|
||
// 5. If the snapshot containing block size exceeds an implementation-defined maximum, then return failure.
|
||
auto snapshot_containing_block = document.navigable()->snapshot_containing_block();
|
||
if (snapshot_containing_block.width() > NumericLimits<int>::max() || snapshot_containing_block.height() > NumericLimits<int>::max())
|
||
return Error::from_string_literal("The snapshot containing block is too large.");
|
||
|
||
// 6. Set transition’s initial snapshot containing block size to the snapshot containing block size.
|
||
m_initial_snapshot_containing_block_size = snapshot_containing_block.size();
|
||
|
||
// 7. For each element of every element that is connected, and has a node document equal to document, in paint
|
||
// order:
|
||
// FIXME: Actually do this in paint order
|
||
auto result = document.document_element()->for_each_in_inclusive_subtree_of_type<DOM::Element>([&](auto& element) {
|
||
// NOTE: Step 1 is handled at the end of this function.
|
||
|
||
// 2. If element has more than one box fragment, then continue.
|
||
// FIXME: Implement this once we have fragments.
|
||
|
||
// 3. Let transitionName be the element’s document-scoped view transition name.
|
||
auto transition_name = element.document_scoped_view_transition_name();
|
||
|
||
// 4. If transitionName is none, or element is not rendered, then continue.
|
||
if (!transition_name.has_value() || element.not_rendered())
|
||
return TraversalDecision::Continue;
|
||
|
||
// 5. If usedTransitionNames contains transitionName, then:
|
||
if (used_transition_names.contains(transition_name.value())) {
|
||
// 1. For each element in captureElements:
|
||
for (auto& element : capture_elements)
|
||
// 1. Set element’s captured in a view transition to false.
|
||
element.set_captured_in_a_view_transition(false);
|
||
|
||
// 2. Return failure
|
||
return TraversalDecision::Break;
|
||
}
|
||
|
||
// 6. Append transitionName to usedTransitionNames.
|
||
used_transition_names.set(transition_name.value());
|
||
|
||
// 7. Set element’s captured in a view transition to true.
|
||
element.set_captured_in_a_view_transition(true);
|
||
|
||
// 8. Append element to captureElements.
|
||
capture_elements.append(element);
|
||
|
||
// 1. If any flat tree ancestor of this element skips its contents, then continue.
|
||
if (element.skips_its_contents())
|
||
return TraversalDecision::SkipChildrenAndContinue;
|
||
|
||
return TraversalDecision::Continue;
|
||
});
|
||
if (result == TraversalDecision::Break)
|
||
return Error::from_string_literal("Cannot include multiple elements with the same view-transition-name in a view transition.");
|
||
|
||
// 8. For each element in captureElements:
|
||
for (auto& element : capture_elements) {
|
||
// 1. Let capture be a new captured element struct.
|
||
auto capture = heap().allocate<CapturedElement>();
|
||
|
||
// 2. Set capture’s old image to the result of capturing the image of element.
|
||
capture->old_image = element.capture_the_image();
|
||
|
||
// 3. Let originalRect be snapshot containing block if element is the document element, otherwise, the
|
||
// element's border box.
|
||
auto original_rect = element.is_document_element() ? snapshot_containing_block : element.paintable_box()->absolute_border_box_rect();
|
||
|
||
// 4. Set capture’s old width to originalRect’s width.
|
||
capture->old_width = original_rect.width();
|
||
|
||
// 5. Set capture’s old height to originalRect’s height.
|
||
capture->old_height = original_rect.height();
|
||
|
||
// 6. Set capture’s old transform to a <transform-function> that would map element’s border box from the
|
||
// snapshot containing block origin to its current visual position.
|
||
// FIXME: Actually compute the right transform here.
|
||
capture->old_transform = CSS::Transformation(CSS::TransformFunction::Translate, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length(0, CSS::Length::Type::Px)), CSS::TransformValue(CSS::Length(0, CSS::Length::Type::Px)) }));
|
||
|
||
// 7. Set capture’s old writing-mode to the computed value of writing-mode on element.
|
||
capture->old_writing_mode = element.layout_node()->computed_values().writing_mode();
|
||
|
||
// 8. Set capture’s old direction to the computed value of direction on element.
|
||
capture->old_direction = element.layout_node()->computed_values().direction();
|
||
|
||
// 9. Set capture’s old text-orientation to the computed value of text-orientation on element.
|
||
// FIXME: Implement this once we have text-orientation.
|
||
|
||
// 10. Set capture’s old mix-blend-mode to the computed value of mix-blend-mode on element.
|
||
capture->old_mix_blend_mode = element.layout_node()->computed_values().mix_blend_mode();
|
||
|
||
// 11. Set capture’s old backdrop-filter to the computed value of backdrop-filter on element.
|
||
capture->old_backdrop_filter = element.layout_node()->computed_values().backdrop_filter();
|
||
|
||
// 12. Set capture’s old color-scheme to the computed value of color-scheme on element.
|
||
capture->old_color_scheme = element.layout_node()->computed_values().color_scheme();
|
||
|
||
// 13. Let transitionName be the computed value of view-transition-name for element.
|
||
auto transition_name = element.layout_node()->computed_values().view_transition_name();
|
||
|
||
// 14. Set namedElements[transitionName] to capture.
|
||
named_elements.set(transition_name.value(), capture);
|
||
}
|
||
|
||
// 9. For each element in captureElements:
|
||
for (auto& element : capture_elements) {
|
||
// 1. Set element’s captured in a view transition to false.
|
||
element.set_captured_in_a_view_transition(false);
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#capture-the-new-state
|
||
ErrorOr<void> ViewTransition::capture_the_new_state()
|
||
{
|
||
// To capture the new state for ViewTransition transition:
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Let namedElements be transition’s named elements.
|
||
// NOTE: We just use m_named_elements
|
||
|
||
// 3. Let usedTransitionNames be a new set of strings.
|
||
auto used_transition_names = AK::OrderedHashTable<FlyString>();
|
||
|
||
// 4. For each element of every element that is connected, and has a node document equal to document, in paint
|
||
// order:
|
||
// FIXME: Actually do this in paint order
|
||
auto result = document.document_element()->for_each_in_inclusive_subtree_of_type<DOM::Element>([&](auto& element) {
|
||
// NOTE: Step 1 is handled at the end of this function.
|
||
|
||
// 2. Let transitionName be the element’s document-scoped view transition name.
|
||
auto transition_name = element.document_scoped_view_transition_name();
|
||
|
||
// 3. If transitionName is none, or element is not rendered, then continue.
|
||
if (!transition_name.has_value() || element.not_rendered())
|
||
return TraversalDecision::Continue;
|
||
|
||
// 4. If element has more than one box fragment, then continue.
|
||
// FIXME: Implement this once we have fragments
|
||
|
||
// 5. If usedTransitionNames contains transitionName, then return failure.
|
||
if (used_transition_names.contains(transition_name.value()))
|
||
return TraversalDecision::Break;
|
||
|
||
// 6. Append transitionName to usedTransitionNames.
|
||
used_transition_names.set(transition_name.value());
|
||
|
||
// 7. If namedElements[transitionName] does not exist, then set namedElements[transitionName] to a new captured element struct.
|
||
if (!m_named_elements.contains(transition_name.value())) {
|
||
auto captured_element = heap().allocate<CapturedElement>();
|
||
m_named_elements.set(transition_name.value(), captured_element);
|
||
}
|
||
|
||
// 8. Set namedElements[transitionName]'s new element to element.
|
||
m_named_elements.get(transition_name.value()).value()->new_element = element;
|
||
|
||
// 1. If any flat tree ancestor of this element skips its contents, then continue.
|
||
if (element.skips_its_contents())
|
||
return TraversalDecision::SkipChildrenAndContinue;
|
||
|
||
return TraversalDecision::Continue;
|
||
});
|
||
if (result == TraversalDecision::Break)
|
||
return Error::from_string_literal("Cannot include multiple elements with the same view-transition-name in a view transition.");
|
||
|
||
return {};
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#setup-transition-pseudo-elements
|
||
void ViewTransition::setup_transition_pseudo_elements()
|
||
{
|
||
// To setup transition pseudo-elements for a ViewTransition transition:
|
||
|
||
// 1. Let document be this’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Set document’s show view transition tree to true.
|
||
document.set_show_view_transition_tree(true);
|
||
// Note: stylesheet is not a variable in the spec but ends up being referenced a lot in this algorithm.
|
||
auto stylesheet = document.dynamic_view_transition_style_sheet();
|
||
|
||
// 3. For each transitionName → capturedElement of transition’s named elements:
|
||
for (auto [transition_name, captured_element] : m_named_elements) {
|
||
// 1. Let group be a new '::view-transition-group()', with its view transition name set to transitionName.
|
||
auto group = heap().allocate<NamedViewTransitionPseudoElement>(CSS::PseudoElement::ViewTransitionGroup, transition_name);
|
||
|
||
// 2. Append group to transition’s transition root pseudo-element.
|
||
m_transition_root_pseudo_element->append_child(group);
|
||
|
||
// 3. Let imagePair be a new '::view-transition-image-pair()', with its view transition name set to
|
||
// transitionName.
|
||
auto image_pair = heap().allocate<NamedViewTransitionPseudoElement>(CSS::PseudoElement::ViewTransitionImagePair, transition_name);
|
||
|
||
// 4. Append imagePair to group.
|
||
group->append_child(image_pair);
|
||
|
||
// 5. If capturedElement’s old image is not null, then:
|
||
if (captured_element->old_image) {
|
||
// 1. Let old be a new '::view-transition-old()', with its view transition name set to transitionName,
|
||
// displaying capturedElement’s old image as its replaced content.
|
||
auto old = heap().allocate<ReplacedNamedViewTransitionPseudoElement>(CSS::PseudoElement::ViewTransitionOld, transition_name, captured_element->old_image);
|
||
|
||
// 2. Append old to imagePair.
|
||
image_pair->append_child(old);
|
||
}
|
||
|
||
// 6. If capturedElement’s new element is not null, then:
|
||
if (captured_element->new_element) {
|
||
// 1. Let new be a new ::view-transition-new(), with its view transition name set to transitionName.
|
||
// NOTE: The styling of this pseudo is handled in update pseudo-element styles.
|
||
auto new_ = heap().allocate<ReplacedNamedViewTransitionPseudoElement>(CSS::PseudoElement::ViewTransitionNew, transition_name);
|
||
|
||
// 2. Append new to imagePair.
|
||
image_pair->append_child(new_);
|
||
}
|
||
|
||
// 7. If capturedElement’s old image is null, then:
|
||
if (!captured_element->old_image) {
|
||
// 1. Assert: capturedElement’s new element is not null.
|
||
VERIFY(captured_element->new_element);
|
||
|
||
// 2. Set capturedElement’s image animation name rule to a new CSSStyleRule representing the
|
||
// following CSS, and append it to document’s dynamic view transition style sheet:
|
||
// :root::view-transition-new(transitionName) {
|
||
// animation-name: -ua-view-transition-fade-in;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
unsigned index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root::view-transition-new({}) {{
|
||
animation-name: -ua-view-transition-fade-in;
|
||
}}
|
||
)",
|
||
transition_name)),
|
||
stylesheet->rules().length()));
|
||
captured_element->image_animation_name_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
}
|
||
|
||
// 8. If capturedElement’s new element is null, then:
|
||
if (!captured_element->new_element) {
|
||
// 1. Assert: capturedElement’s old image is not null.
|
||
VERIFY(captured_element->old_image);
|
||
|
||
// 2. Set capturedElement’s image animation name rule to a new CSSStyleRule representing the
|
||
// following CSS, and append it to document’s dynamic view transition style sheet:
|
||
// :root::view-transition-old(transitionName) {
|
||
// animation-name: -ua-view-transition-fade-out;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
unsigned index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root::view-transition-old({}) {{
|
||
animation-name: -ua-view-transition-fade-out;
|
||
}}
|
||
)",
|
||
transition_name)),
|
||
stylesheet->rules().length()));
|
||
captured_element->image_animation_name_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
}
|
||
|
||
// 9. If both of capturedElement’s old image and new element are not null, then:
|
||
if (captured_element->old_image && captured_element->new_element) {
|
||
// 1. Let transform be capturedElement’s old transform.
|
||
auto& transform = captured_element->old_transform;
|
||
// FIXME: Remove this once tranform gets used in step 5 below.
|
||
(void)transform;
|
||
|
||
// 2. Let width be capturedElement’s old width.
|
||
auto& width = captured_element->old_width;
|
||
|
||
// 3. Let height be capturedElement’s old height.
|
||
auto& height = captured_element->old_height;
|
||
|
||
// 4. Let backdropFilter be capturedElement’s old backdrop-filter.
|
||
auto& backdrop_filter = captured_element->old_backdrop_filter;
|
||
// FIXME: Remove this once tranform gets used in step 5 below.
|
||
(void)backdrop_filter;
|
||
|
||
// 5. Set capturedElement’s group keyframes to a new CSSKeyframesRule representing the following
|
||
// CSS, and append it to document’s dynamic view transition style sheet:
|
||
// @keyframes -ua-view-transition-group-anim-transitionName {
|
||
// from {
|
||
// transform: transform;
|
||
// width: width;
|
||
// height: height;
|
||
// backdrop-filter: backdropFilter;
|
||
// }
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
unsigned index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
@keyframes -ua-view-transition-group-anim-{} {{
|
||
from {{
|
||
transform: {};
|
||
width: {};
|
||
height: {};
|
||
backdrop-filter: {};
|
||
}}
|
||
}}
|
||
)",
|
||
transition_name, "transform", width, height, "backdrop_filter")),
|
||
stylesheet->rules().length()));
|
||
// FIXME: all the strings above should be the identically named variables, serialized somehow.
|
||
captured_element->group_keyframes = as<CSS::CSSKeyframesRule>(stylesheet->css_rules()->item(0));
|
||
|
||
// 6. Set capturedElement’s group animation name rule to a new CSSStyleRule representing the
|
||
// following CSS, and append it to document’s dynamic view transition style sheet:
|
||
// :root::view-transition-group(transitionName) {
|
||
// animation-name: -ua-view-transition-group-anim-transitionName;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root::view-transition-group({0}) {{
|
||
animation-name: -ua-view-transition-group-anim-{0};
|
||
}}
|
||
)",
|
||
transition_name)),
|
||
stylesheet->rules().length()));
|
||
captured_element->group_animation_name_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
|
||
// 7. Set capturedElement’s image pair isolation rule to a new CSSStyleRule representing the
|
||
// following CSS, and append it to document’s dynamic view transition style sheet:
|
||
// :root::view-transition-image-pair(transitionName) {
|
||
// isolation: isolate;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root::view-transition-image-pair({}) {{
|
||
isolation: isolate;
|
||
}}
|
||
)",
|
||
transition_name)),
|
||
stylesheet->rules().length()));
|
||
captured_element->image_pair_isolation_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
|
||
// 8. Set capturedElement’s image animation name rule to a new CSSStyleRule representing the
|
||
// following CSS, and append it to document’s dynamic view transition style sheet:
|
||
// :root::view-transition-old(transitionName) {
|
||
// animation-name: -ua-view-transition-fade-out, -ua-mix-blend-mode-plus-lighter;
|
||
// }
|
||
// :root::view-transition-new(transitionName) {
|
||
// animation-name: -ua-view-transition-fade-in, -ua-mix-blend-mode-plus-lighter;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
// NOTE: mix-blend-mode: plus-lighter ensures that the blending of identical pixels from the
|
||
// old and new images results in the same color value as those pixels, and achieves a “correct”
|
||
// cross-fade.
|
||
// AD-HOC: We can't use the given CSS exactly since it is two rules, not one.
|
||
// Instead we turn it into one rule, with both of them nested inside.
|
||
index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root {{
|
||
&::view-transition-old({0}) {{
|
||
animation-name: -ua-view-transition-fade-out, -ua-mix-blend-mode-plus-lighter;
|
||
}}
|
||
&::view-transition-new({0}) {{
|
||
animation-name: -ua-view-transition-fade-in, -ua-mix-blend-mode-plus-lighter;
|
||
}}
|
||
}}
|
||
)",
|
||
transition_name)),
|
||
stylesheet->rules().length()));
|
||
captured_element->image_animation_name_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
}
|
||
}
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#call-the-update-callback
|
||
void ViewTransition::call_the_update_callback()
|
||
{
|
||
auto& realm = this->realm();
|
||
// To call the update callback of a ViewTransition transition:
|
||
|
||
// 1. Assert: transition’s phase is "done", or before "update-callback-called".
|
||
VERIFY(m_phase == Phase::Done || to_underlying(m_phase) < to_underlying(Phase::UpdateCallbackCalled));
|
||
|
||
// 2. If transition’s phase is not "done", then set transition’s phase to "update-callback-called".
|
||
if (m_phase != Phase::Done)
|
||
m_phase = Phase::UpdateCallbackCalled;
|
||
|
||
// 3. Let callbackPromise be null.
|
||
WebIDL::Promise* callback_promise;
|
||
|
||
// 4. If transition’s update callback is null, then set callbackPromise to a promise resolved with undefined, in
|
||
// transition’s relevant Realm.
|
||
if (!m_update_callback) {
|
||
auto& relevant_realm = HTML::relevant_realm(*this);
|
||
callback_promise = WebIDL::create_promise(relevant_realm);
|
||
WebIDL::resolve_promise(relevant_realm, *callback_promise, JS::js_undefined());
|
||
}
|
||
|
||
// 5. Otherwise, set callbackPromise to the result of invoking transition’s update callback.
|
||
else {
|
||
auto promise = MUST(WebIDL::invoke_callback(*m_update_callback, {}, {}));
|
||
// FIXME: since WebIDL::invoke_callback does not yet convert the value for us,
|
||
// We need to do it here manually.
|
||
// https://webidl.spec.whatwg.org/#js-promise
|
||
|
||
// 1. Let promiseCapability be ? NewPromiseCapability(%Promise%).
|
||
auto promise_capability = WebIDL::create_promise(realm);
|
||
// 2. Perform ? Call(promiseCapability.[[Resolve]], undefined, « V »).
|
||
// FIXME: We should not need to push an incumbent realm here, but http://wpt.live/css/css-view-transitions/update-callback-timeout.html crashes without it.
|
||
HTML::main_thread_event_loop().push_onto_backup_incumbent_realm_stack(realm);
|
||
MUST(JS::call(realm.vm(), *promise_capability->resolve(), JS::js_undefined(), promise));
|
||
HTML::main_thread_event_loop().pop_backup_incumbent_realm_stack();
|
||
// 3. Return promiseCapability.
|
||
callback_promise = GC::make_root(promise_capability);
|
||
}
|
||
|
||
// 6. Let fulfillSteps be to following steps:
|
||
auto fulfill_steps = GC::create_function(realm.heap(), [this, &realm](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||
// 1. Resolve transition’s update callback done promise with undefined.
|
||
WebIDL::resolve_promise(realm, m_update_callback_done_promise, JS::js_undefined());
|
||
|
||
// 2. Activate transition.
|
||
activate_view_transition();
|
||
|
||
return JS::js_undefined();
|
||
});
|
||
|
||
// 7. Let rejectSteps be the following steps given reason:
|
||
auto reject_steps = GC::create_function(realm.heap(), [this, &realm](JS::Value reason) -> WebIDL::ExceptionOr<JS::Value> {
|
||
// 1. Reject transition’s update callback done promise with reason.
|
||
WebIDL::reject_promise(realm, m_update_callback_done_promise, reason);
|
||
|
||
// 2. If transition’s phase is "done", then return.
|
||
// NOTE: This happens if transition was skipped before this point.
|
||
if (m_phase == Phase::Done)
|
||
return JS::js_undefined();
|
||
|
||
// 3. Mark as handled transition’s ready promise.
|
||
// NOTE: transition’s update callback done promise will provide the unhandledrejection. This
|
||
// step avoids a duplicate.
|
||
WebIDL::mark_promise_as_handled(m_update_callback_done_promise);
|
||
|
||
// 4. Skip the view transition transition with reason.
|
||
skip_the_view_transition(reason);
|
||
|
||
return JS::js_undefined();
|
||
});
|
||
|
||
// 8. React to callbackPromise with fulfillSteps and rejectSteps.
|
||
// AD-HOC: This can cause an assertion failure when the reaction algorithm ends up accessing the incumbent realm, which may not exist here.
|
||
// For now, lets just manually push something onto the incumbent realm stack here as a hack.
|
||
// A spec bug for this has been filed at https://github.com/w3c/csswg-drafts/issues/11990
|
||
HTML::main_thread_event_loop().push_onto_backup_incumbent_realm_stack(realm);
|
||
WebIDL::react_to_promise(*callback_promise, fulfill_steps, reject_steps);
|
||
HTML::main_thread_event_loop().pop_backup_incumbent_realm_stack();
|
||
|
||
// 9. To skip a transition after a timeout, the user agent may perform the following steps in parallel:
|
||
// FIXME: Figure out if we want to do this.
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#schedule-the-update-callback
|
||
void ViewTransition::schedule_the_update_callback()
|
||
{
|
||
// To schedule the update callback given a ViewTransition transition:
|
||
|
||
// 1. Append transition to transition’s relevant settings object’s update callback queue.
|
||
// AD-HOC: The update callback queue is a property on document, not a settings object.
|
||
// For now we'll just put it on the relevant global object's associated document.
|
||
// Spec bug is filed at https://github.com/w3c/csswg-drafts/issues/11986
|
||
as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document().update_callback_queue().append(this);
|
||
|
||
// 2. Queue a global task on the DOM manipulation task source, given transition’s relevant global object, to flush
|
||
// the update callback queue.
|
||
HTML::queue_global_task(HTML::Task::Source::DOMManipulation, HTML::relevant_global_object(*this), GC::create_function(realm().heap(), [&] {
|
||
// AD-HOC: Spec doesn't say what document to flush it for.
|
||
// Lets just use the one we use elsewhere.
|
||
// (see https://github.com/w3c/csswg-drafts/issues/11986 )
|
||
as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document().flush_the_update_callback_queue();
|
||
}));
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#skip-the-view-transition
|
||
void ViewTransition::skip_the_view_transition(JS::Value reason)
|
||
{
|
||
auto& realm = this->realm();
|
||
// To skip the view transition for ViewTransition transition with reason reason:
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Assert: transition’s phase is not "done".
|
||
VERIFY(m_phase != Phase::Done);
|
||
|
||
// 3. If transition’s phase is before "update-callback-called", then schedule the update callback for transition.
|
||
if (to_underlying(m_phase) < to_underlying(Phase::UpdateCallbackCalled)) {
|
||
schedule_the_update_callback();
|
||
}
|
||
|
||
// 4. Set rendering suppression for view transitions to false.
|
||
document.set_rendering_suppression_for_view_transitions(false);
|
||
|
||
// 5. If document’s active view transition is transition, Clear view transition transition.
|
||
if (document.active_view_transition() == this)
|
||
clear_view_transition();
|
||
|
||
// 6. Set transition’s phase to "done".
|
||
m_phase = Phase::Done;
|
||
|
||
// 7. Reject transition’s ready promise with reason.
|
||
WebIDL::reject_promise(realm, m_ready_promise, reason);
|
||
|
||
// 8. Resolve transition’s finished promise with the result of reacting to transition’s update callback done promise:
|
||
// - If the promise was fulfilled, then return undefined.
|
||
// AD-HOC: This can cause an assertion failure when the reaction algorithm ends up accessing the incumbent realm, which may not exist here.
|
||
// For now, lets just manually push something onto the incumbent realm stack here as a hack.
|
||
// A spec bug for this has been filed at https://github.com/w3c/csswg-drafts/issues/11990
|
||
HTML::main_thread_event_loop().push_onto_backup_incumbent_realm_stack(realm);
|
||
WebIDL::resolve_promise(realm, m_finished_promise, WebIDL::react_to_promise(m_update_callback_done_promise, GC::create_function(realm.heap(), [](JS::Value) -> WebIDL::ExceptionOr<JS::Value> { return JS::js_undefined(); }), nullptr)->promise());
|
||
HTML::main_thread_event_loop().pop_backup_incumbent_realm_stack();
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#handle-transition-frame
|
||
void ViewTransition::handle_transition_frame()
|
||
{
|
||
auto& realm = this->realm();
|
||
// To handle transition frame given a ViewTransition transition
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Let hasActiveAnimations be a boolean, initially false.
|
||
bool has_active_animations = false;
|
||
|
||
// 3. For each element of transition’s transition root pseudo-element’s inclusive descendants:
|
||
m_transition_root_pseudo_element->for_each_in_inclusive_subtree([&](DOM::PseudoElementTreeNode&) {
|
||
// For each animation whose timeline is a document timeline associated with document, and contains at
|
||
// least one associated effect whose effect target is element, set hasActiveAnimations to true if any of the
|
||
// following conditions are true:
|
||
// FIXME: Implement this.
|
||
|
||
// - animation’s play state is paused or running.
|
||
// FIXME: Implement this.
|
||
|
||
// - document’s pending animation event queue has any events associated with animation.
|
||
// FIXME: Implement this.
|
||
|
||
return TraversalDecision::Continue;
|
||
});
|
||
|
||
// 4. If hasActiveAnimations is false:
|
||
if (!has_active_animations) {
|
||
// 1. Set transition’s phase to "done".
|
||
m_phase = Phase::Done;
|
||
|
||
// 2. Clear view transition transition.
|
||
clear_view_transition();
|
||
|
||
// 3. Resolve transition’s finished promise.
|
||
// FIXME: Without this TemporaryExecutionContext, this would fail an assert later on about missing one.
|
||
// Figure out why and where this actually needs to be handled.
|
||
HTML::TemporaryExecutionContext context(realm);
|
||
WebIDL::resolve_promise(realm, m_finished_promise);
|
||
|
||
// 4. Return.
|
||
return;
|
||
}
|
||
|
||
// 5. If transition’s initial snapshot containing block size is not equal to the snapshot containing block size,
|
||
auto snapshot_containing_block_size = document.navigable()->snapshot_containing_block_size();
|
||
if (m_initial_snapshot_containing_block_size != snapshot_containing_block_size) {
|
||
// then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm,
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Transition's initial snapshot containing block size is not equal to the snapshot containing block size"_utf16));
|
||
// and return.
|
||
return;
|
||
}
|
||
|
||
// 6. Update pseudo-element styles for transition.
|
||
auto result = update_pseudo_element_styles();
|
||
// If failure is returned,
|
||
if (result.is_error()) {
|
||
// then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm,
|
||
skip_the_view_transition(WebIDL::InvalidStateError::create(realm, "Failed to update pseudo-element styles"_utf16));
|
||
// and return.
|
||
return;
|
||
}
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#update-pseudo-element-styles
|
||
ErrorOr<void> ViewTransition::update_pseudo_element_styles()
|
||
{
|
||
// To update pseudo-element styles for a ViewTransition transition:
|
||
|
||
// 1. For each transitionName → capturedElement of transition’s named elements:
|
||
for (auto [transition_name, captured_element] : m_named_elements) {
|
||
// 1. Let width, height, transform, writingMode, direction, textOrientation, mixBlendMode, backdropFilter and
|
||
// colorScheme be null.
|
||
Optional<CSSPixels> width = {};
|
||
Optional<CSSPixels> height = {};
|
||
Optional<CSS::Transformation> transform = {};
|
||
Optional<CSS::WritingMode> writing_mode = {};
|
||
Optional<CSS::Direction> direction = {};
|
||
// FIXME: Implement this once we have text-orientation.
|
||
Optional<CSS::MixBlendMode> mix_blend_mode = {};
|
||
Optional<CSS::Filter> backdrop_filter = {};
|
||
Optional<CSS::PreferredColorScheme> color_scheme = {};
|
||
|
||
// 2. If capturedElement’s new element is null, then:
|
||
if (!captured_element->new_element) {
|
||
// 1. Set width to capturedElement’s old width.
|
||
width = captured_element->old_width;
|
||
|
||
// 2. Set height to capturedElement’s old height.
|
||
height = captured_element->old_height;
|
||
|
||
// 3. Set transform to capturedElement’s old transform.
|
||
transform = captured_element->old_transform;
|
||
|
||
// 4. Set writingMode to capturedElement’s old writing-mode.
|
||
writing_mode = captured_element->old_writing_mode;
|
||
|
||
// 5. Set direction to capturedElement’s old direction.
|
||
direction = captured_element->old_direction;
|
||
|
||
// 6. Set textOrientation to capturedElement’s old text-orientation.
|
||
// FIXME: Implement this once we have text-orientation.
|
||
|
||
// 7. Set mixBlendMode to capturedElement’s old mix-blend-mode.
|
||
mix_blend_mode = captured_element->old_mix_blend_mode;
|
||
|
||
// 8. Set backdropFilter to capturedElement’s old backdrop-filter.
|
||
backdrop_filter = captured_element->old_backdrop_filter;
|
||
|
||
// 9. Set colorScheme to capturedElement’s old color-scheme.
|
||
color_scheme = captured_element->old_color_scheme;
|
||
}
|
||
|
||
// 3. Otherwise:
|
||
else {
|
||
// 1. Return failure if any of the following conditions is true:
|
||
|
||
// - capturedElement’s new element has a flat tree ancestor that skips its contents.
|
||
for (auto ancestor = captured_element->new_element->parent_element(); ancestor; ancestor = ancestor->parent_element()) {
|
||
if (ancestor->skips_its_contents())
|
||
return Error::from_string_literal("capturedElement’s new element has a flat tree ancestor that skips its contents.");
|
||
}
|
||
|
||
// - capturedElement’s new element is not rendered.
|
||
if (captured_element->new_element->not_rendered())
|
||
return Error::from_string_literal("capturedElement’s new element is not rendered.");
|
||
|
||
// - capturedElement has more than one box fragment.
|
||
// FIXME: Implement this once we have fragments.
|
||
// FIXME: capturedElement would not have box fragments. Update this once the spec issue for that has been resolved:
|
||
// https://github.com/w3c/csswg-drafts/issues/11991
|
||
|
||
// NOTE: Other rendering constraints are enforced via capturedElement’s new element being
|
||
// captured in a view transition.
|
||
|
||
// 2. Let newRect be the snapshot containing block if capturedElement’s new element is the
|
||
// document element, otherwise, capturedElement’s border box.
|
||
auto new_rect = captured_element->new_element->is_document_element() ? captured_element->new_element->navigable()->snapshot_containing_block() : captured_element->new_element->paintable_box()->absolute_border_box_rect();
|
||
|
||
// 3. Set width to the current width of newRect.
|
||
width = new_rect.width();
|
||
|
||
// 4. Set height to the current height of newRect.
|
||
height = new_rect.height();
|
||
|
||
// 5. Set transform to a transform that would map newRect from the snapshot containing block origin
|
||
// to its current visual position.
|
||
auto offset = new_rect.location() - captured_element->new_element->navigable()->snapshot_containing_block().location();
|
||
transform = CSS::Transformation(CSS::TransformFunction::Translate, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(offset.x())), CSS::TransformValue(CSS::Length::make_px(offset.y())) }));
|
||
|
||
// 6. Set writingMode to the computed value of writing-mode on capturedElement’s new element.
|
||
writing_mode = captured_element->new_element->layout_node()->computed_values().writing_mode();
|
||
|
||
// 7. Set direction to the computed value of direction on capturedElement’s new element.
|
||
direction = captured_element->new_element->layout_node()->computed_values().direction();
|
||
|
||
// 8. Set textOrientation to the computed value of text-orientation on capturedElement’s new
|
||
// element.
|
||
// FIXME: Implement this.
|
||
|
||
// 9. Set mixBlendMode to the computed value of mix-blend-mode on capturedElement’s new
|
||
// element.
|
||
mix_blend_mode = captured_element->new_element->layout_node()->computed_values().mix_blend_mode();
|
||
|
||
// 10. Set backdropFilter to the computed value of backdrop-filter on capturedElement’s new element.
|
||
backdrop_filter = captured_element->new_element->layout_node()->computed_values().backdrop_filter();
|
||
|
||
// 11. Set colorScheme to the computed value of color-scheme on capturedElement’s new element.
|
||
color_scheme = captured_element->new_element->layout_node()->computed_values().color_scheme();
|
||
}
|
||
|
||
// 4. If capturedElement’s group styles rule is null, then set capturedElement’s group styles rule to a new
|
||
// CSSStyleRule representing the following CSS, and append it to transition’s relevant global object’s
|
||
// associated document’s dynamic view transition style sheet.
|
||
if (!captured_element->group_styles_rule) {
|
||
// :root::view-transition-group(transitionName) {
|
||
// width: width;
|
||
// height: height;
|
||
// transform: transform;
|
||
// writing-mode: writingMode;
|
||
// direction: direction;
|
||
// text-orientation: textOrientation;
|
||
// mix-blend-mode: mixBlendMode;
|
||
// backdrop-filter: backdropFilter;
|
||
// color-scheme: colorScheme;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
auto stylesheet = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document().dynamic_view_transition_style_sheet();
|
||
unsigned index = MUST(stylesheet->insert_rule(MUST(String::formatted(R"(
|
||
:root::view-transition-group({}) {{
|
||
width: {};
|
||
height: {};
|
||
transform: {};
|
||
writing-mode: {};
|
||
direction: {};
|
||
text-orientation: {};
|
||
mix-blend-mode: {};
|
||
backdrop-filter: {};
|
||
color-scheme: {};
|
||
}}
|
||
)",
|
||
transition_name, width, height, "transform", "writing_mode", "direction", "text_orientation", "mix_blend_mode", "backdrop_filter", "color_scheme")),
|
||
stylesheet->rules().length()));
|
||
// FIXME: all the strings above should be the identically named variables, serialized somehow.
|
||
captured_element->group_styles_rule = as<CSS::CSSStyleRule>(stylesheet->css_rules()->item(index));
|
||
}
|
||
// Otherwise, update capturedElement’s group styles rule to match the following CSS:
|
||
// :root::view-transition-group(transitionName) {
|
||
// width: width;
|
||
// height: height;
|
||
// transform: transform;
|
||
// writing-mode: writingMode;
|
||
// direction: direction;
|
||
// text-orientation: textOrientation;
|
||
// mix-blend-mode: mixBlendMode;
|
||
// backdrop-filter: backdropFilter;
|
||
// color-scheme: colorScheme;
|
||
// }
|
||
// NOTE: The above code example contains variables to be replaced.
|
||
else {
|
||
captured_element->group_styles_rule->set_selector_text(MUST(String::formatted(":root::view-transition-group({0})", transition_name)));
|
||
captured_element->group_styles_rule->set_css_text(MUST(String::formatted(R"(
|
||
width: {};
|
||
height: {};
|
||
transform: {};
|
||
writing-mode: {};
|
||
direction: {};
|
||
text-orientation: {};
|
||
mix-blend-mode: {};
|
||
backdrop-filter: {};
|
||
color-scheme: {};
|
||
)",
|
||
width, height, "transform", "writing_mode", "direction", "text_orientation", "mix_blend_mode", "backdrop_filter", "color_scheme")));
|
||
// FIXME: all the strings above should be the identically named variables, serialized somehow.
|
||
}
|
||
|
||
// 5. If capturedElement’s new element is not null, then:
|
||
if (captured_element->new_element) {
|
||
// 1. Let new be the ::view-transition-new() with the view transition name transitionName.
|
||
ReplacedNamedViewTransitionPseudoElement* new_;
|
||
m_transition_root_pseudo_element->for_each_in_inclusive_subtree_of_type<ReplacedNamedViewTransitionPseudoElement>([&](auto& element) {
|
||
if (element.m_type == CSS::PseudoElement::ViewTransitionNew && element.m_view_transition_name == transition_name) {
|
||
new_ = &element;
|
||
return TraversalDecision::Break;
|
||
}
|
||
return TraversalDecision::Continue;
|
||
});
|
||
|
||
// 2. Set new’s replaced element content to the result of capturing the image of capturedElement’s
|
||
// new element.
|
||
new_->m_content = captured_element->new_element->capture_the_image();
|
||
}
|
||
}
|
||
|
||
// This algorithm must be executed to update styles in user-agent origin if its effects can be observed by a web API.
|
||
// FIXME: Find all the places where this is relevant.
|
||
|
||
return {};
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-view-transitions-1/#clear-view-transition
|
||
void ViewTransition::clear_view_transition()
|
||
{
|
||
// To clear view transition of a ViewTransition transition:
|
||
|
||
// 1. Let document be transition’s relevant global object’s associated document.
|
||
auto& document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
|
||
|
||
// 2. Assert: document’s active view transition is transition.
|
||
VERIFY(document.active_view_transition() == this);
|
||
|
||
// 3. For each capturedElement of transition’s named elements' values:
|
||
for (auto captured_element : m_named_elements) {
|
||
// 1. If capturedElement’s new element is not null, then set capturedElement’s new element's captured in a
|
||
// view transition to false.
|
||
if (captured_element.value->new_element) {
|
||
captured_element.value->new_element->set_captured_in_a_view_transition(false);
|
||
}
|
||
|
||
// 2. For each style of capturedElement’s style definitions:
|
||
auto steps = [&](GC::Ptr<CSS::CSSRule> style) {
|
||
// 1. If style is not null, and style is in document’s dynamic view transition style sheet, then remove
|
||
// style from document’s dynamic view transition style sheet.
|
||
if (style) {
|
||
auto stylesheet = document.dynamic_view_transition_style_sheet();
|
||
auto rules = stylesheet->css_rules();
|
||
for (u32 i = 0; i < rules->length(); i++) {
|
||
if (rules->item(i) == style) {
|
||
MUST(stylesheet->delete_rule(i));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
steps(captured_element.value->group_keyframes);
|
||
steps(captured_element.value->group_animation_name_rule);
|
||
steps(captured_element.value->group_styles_rule);
|
||
steps(captured_element.value->image_pair_isolation_rule);
|
||
steps(captured_element.value->image_animation_name_rule);
|
||
}
|
||
|
||
// 4. Set document’s show view transition tree to false.
|
||
document.set_show_view_transition_tree(false);
|
||
|
||
// 5. Set document’s active view transition to null.
|
||
document.set_active_view_transition(nullptr);
|
||
}
|
||
|
||
}
|