mirror of
https://github.com/servo/servo.git
synced 2025-09-25 14:20:08 +01:00
html: Properly count <image>/<source> insertion/removal steps as the relevant mutations (#39452)
Follow the HTML specification and take into account that the HTML `<image>/<source>` element inserting/removal steps should only be counted as relevant mutations for `<image>` element if the parent of the inclusive ancestor that was inserted/removed is the parent `<picture>` element. See <https://html.spec.whatwg.org/multipage/#relevant-mutations>. Testing: Improvements in the following tests - html/semantics/embedded-content/the-img-element/relevant-mutations.html Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
parent
c63311af02
commit
99fbd36b5d
6 changed files with 75 additions and 71 deletions
|
@ -737,11 +737,7 @@ impl Element {
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.set_containing_shadow_root(Some(&shadow_root));
|
.set_containing_shadow_root(Some(&shadow_root));
|
||||||
|
|
||||||
let bind_context = BindContext {
|
let bind_context = BindContext::new(&self.node);
|
||||||
tree_connected: self.upcast::<Node>().is_connected(),
|
|
||||||
tree_is_in_a_document_tree: self.upcast::<Node>().is_in_a_document_tree(),
|
|
||||||
tree_is_in_a_shadow_tree: true,
|
|
||||||
};
|
|
||||||
shadow_root.bind_to_tree(&bind_context, can_gc);
|
shadow_root.bind_to_tree(&bind_context, can_gc);
|
||||||
|
|
||||||
let node = self.upcast::<Node>();
|
let node = self.upcast::<Node>();
|
||||||
|
|
|
@ -1926,6 +1926,7 @@ impl VirtualMethods for HTMLImageElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-insertion-steps>
|
||||||
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
||||||
if let Some(s) = self.super_type() {
|
if let Some(s) = self.super_type() {
|
||||||
s.bind_to_tree(context, can_gc);
|
s.bind_to_tree(context, can_gc);
|
||||||
|
@ -1935,23 +1936,24 @@ impl VirtualMethods for HTMLImageElement {
|
||||||
document.register_responsive_image(self);
|
document.register_responsive_image(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The element is inserted into a picture parent element
|
let parent = self.upcast::<Node>().GetParentNode().unwrap();
|
||||||
// https://html.spec.whatwg.org/multipage/#relevant-mutations
|
|
||||||
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
|
// Step 1. If insertedNode's parent is a picture element, then, count this as a relevant
|
||||||
if parent.is::<HTMLPictureElement>() {
|
// mutation for insertedNode.
|
||||||
self.update_the_image_data(can_gc);
|
if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
|
||||||
}
|
self.update_the_image_data(can_gc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#the-img-element:html-element-removing-steps>
|
||||||
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
|
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
|
||||||
self.super_type().unwrap().unbind_from_tree(context, can_gc);
|
self.super_type().unwrap().unbind_from_tree(context, can_gc);
|
||||||
let document = self.owner_document();
|
let document = self.owner_document();
|
||||||
document.unregister_responsive_image(self);
|
document.unregister_responsive_image(self);
|
||||||
|
|
||||||
// The element is removed from a picture parent element
|
// Step 1. If oldParent is a picture element, then, count this as a relevant mutation for
|
||||||
// https://html.spec.whatwg.org/multipage/#relevant-mutations
|
// removedNode.
|
||||||
if context.parent.is::<HTMLPictureElement>() {
|
if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
|
||||||
self.update_the_image_data(can_gc);
|
self.update_the_image_data(can_gc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ use crate::dom::element::AttributeMutation;
|
||||||
use crate::dom::html::htmlelement::HTMLElement;
|
use crate::dom::html::htmlelement::HTMLElement;
|
||||||
use crate::dom::html::htmlimageelement::HTMLImageElement;
|
use crate::dom::html::htmlimageelement::HTMLImageElement;
|
||||||
use crate::dom::html::htmlmediaelement::HTMLMediaElement;
|
use crate::dom::html::htmlmediaelement::HTMLMediaElement;
|
||||||
|
use crate::dom::html::htmlpictureelement::HTMLPictureElement;
|
||||||
use crate::dom::node::{BindContext, Node, UnbindContext};
|
use crate::dom::node::{BindContext, Node, UnbindContext};
|
||||||
use crate::dom::virtualmethods::VirtualMethods;
|
use crate::dom::virtualmethods::VirtualMethods;
|
||||||
use crate::script_runtime::CanGc;
|
use crate::script_runtime::CanGc;
|
||||||
|
@ -81,40 +82,60 @@ impl VirtualMethods for HTMLSourceElement {
|
||||||
&local_name!("sizes") |
|
&local_name!("sizes") |
|
||||||
&local_name!("media") |
|
&local_name!("media") |
|
||||||
&local_name!("type") => {
|
&local_name!("type") => {
|
||||||
let next_sibling_iterator = self.upcast::<Node>().following_siblings();
|
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
|
||||||
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
if parent.is::<HTMLPictureElement>() {
|
||||||
next_sibling_iterator,
|
let next_sibling_iterator = self.upcast::<Node>().following_siblings();
|
||||||
CanGc::note(),
|
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
||||||
);
|
next_sibling_iterator,
|
||||||
|
can_gc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted>
|
/// <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-insertion-steps>
|
||||||
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
||||||
self.super_type().unwrap().bind_to_tree(context, can_gc);
|
self.super_type().unwrap().bind_to_tree(context, can_gc);
|
||||||
let parent = self.upcast::<Node>().GetParentNode().unwrap();
|
|
||||||
if let Some(media) = parent.downcast::<HTMLMediaElement>() {
|
|
||||||
media.handle_source_child_insertion(CanGc::note());
|
|
||||||
}
|
|
||||||
let next_sibling_iterator = self.upcast::<Node>().following_siblings();
|
|
||||||
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
|
||||||
next_sibling_iterator,
|
|
||||||
CanGc::note(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
|
// Step 1. Let parent be insertedNode's parent.
|
||||||
self.super_type().unwrap().unbind_from_tree(context, can_gc);
|
let parent = self.upcast::<Node>().GetParentNode().unwrap();
|
||||||
if let Some(next_sibling) = context.next_sibling {
|
|
||||||
let next_sibling_iterator = next_sibling.inclusively_following_siblings();
|
// Step 2. If parent is a media element that has no src attribute and whose networkState has
|
||||||
|
// the value NETWORK_EMPTY, then invoke that media element's resource selection algorithm.
|
||||||
|
if let Some(media) = parent.downcast::<HTMLMediaElement>() {
|
||||||
|
media.handle_source_child_insertion(can_gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3. If parent is a picture element, then for each child of parent's children, if
|
||||||
|
// child is an img element, then count this as a relevant mutation for child.
|
||||||
|
if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
|
||||||
|
let next_sibling_iterator = self.upcast::<Node>().following_siblings();
|
||||||
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
||||||
next_sibling_iterator,
|
next_sibling_iterator,
|
||||||
CanGc::note(),
|
can_gc,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-removing-steps>
|
||||||
|
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
|
||||||
|
self.super_type().unwrap().unbind_from_tree(context, can_gc);
|
||||||
|
|
||||||
|
// Step 1. If oldParent is a picture element, then for each child of oldParent's children,
|
||||||
|
// if child is an img element, then count this as a relevant mutation for child.
|
||||||
|
if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
|
||||||
|
if let Some(next_sibling) = context.next_sibling {
|
||||||
|
let next_sibling_iterator = next_sibling.inclusively_following_siblings();
|
||||||
|
HTMLSourceElement::iterate_next_html_image_element_siblings(
|
||||||
|
next_sibling_iterator,
|
||||||
|
can_gc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HTMLSourceElementMethods<crate::DomTypeHolder> for HTMLSourceElement {
|
impl HTMLSourceElementMethods<crate::DomTypeHolder> for HTMLSourceElement {
|
||||||
|
|
|
@ -300,6 +300,8 @@ impl Node {
|
||||||
let parent_is_connected = self.is_connected();
|
let parent_is_connected = self.is_connected();
|
||||||
let parent_is_in_ua_widget = self.is_in_ua_widget();
|
let parent_is_in_ua_widget = self.is_in_ua_widget();
|
||||||
|
|
||||||
|
let context = BindContext::new(self);
|
||||||
|
|
||||||
for node in new_child.traverse_preorder(ShadowIncluding::No) {
|
for node in new_child.traverse_preorder(ShadowIncluding::No) {
|
||||||
if parent_in_shadow_tree {
|
if parent_in_shadow_tree {
|
||||||
if let Some(shadow_root) = self.containing_shadow_root() {
|
if let Some(shadow_root) = self.containing_shadow_root() {
|
||||||
|
@ -317,14 +319,7 @@ impl Node {
|
||||||
|
|
||||||
// Out-of-document elements never have the descendants flag set.
|
// Out-of-document elements never have the descendants flag set.
|
||||||
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
|
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
|
||||||
vtable_for(&node).bind_to_tree(
|
vtable_for(&node).bind_to_tree(&context, can_gc);
|
||||||
&BindContext {
|
|
||||||
tree_connected: parent_is_connected,
|
|
||||||
tree_is_in_a_document_tree: parent_is_in_a_document_tree,
|
|
||||||
tree_is_in_a_shadow_tree: parent_in_shadow_tree,
|
|
||||||
},
|
|
||||||
can_gc,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4259,7 +4254,10 @@ impl<'a> ChildrenMutation<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context of the binding to tree of a node.
|
/// The context of the binding to tree of a node.
|
||||||
pub(crate) struct BindContext {
|
pub(crate) struct BindContext<'a> {
|
||||||
|
/// The parent of the inclusive ancestor that was inserted.
|
||||||
|
pub(crate) parent: &'a Node,
|
||||||
|
|
||||||
/// Whether the tree is connected.
|
/// Whether the tree is connected.
|
||||||
///
|
///
|
||||||
/// <https://dom.spec.whatwg.org/#connected>
|
/// <https://dom.spec.whatwg.org/#connected>
|
||||||
|
@ -4274,7 +4272,17 @@ pub(crate) struct BindContext {
|
||||||
pub(crate) tree_is_in_a_shadow_tree: bool,
|
pub(crate) tree_is_in_a_shadow_tree: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BindContext {
|
impl<'a> BindContext<'a> {
|
||||||
|
/// Create a new `BindContext` value.
|
||||||
|
pub(crate) fn new(parent: &'a Node) -> Self {
|
||||||
|
BindContext {
|
||||||
|
parent,
|
||||||
|
tree_connected: parent.is_connected(),
|
||||||
|
tree_is_in_a_document_tree: parent.is_in_a_document_tree(),
|
||||||
|
tree_is_in_a_shadow_tree: parent.is_in_a_shadow_tree(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return true iff the tree is inside either a document- or a shadow tree.
|
/// Return true iff the tree is inside either a document- or a shadow tree.
|
||||||
pub(crate) fn is_in_tree(&self) -> bool {
|
pub(crate) fn is_in_tree(&self) -> bool {
|
||||||
self.tree_is_in_a_document_tree || self.tree_is_in_a_shadow_tree
|
self.tree_is_in_a_document_tree || self.tree_is_in_a_shadow_tree
|
||||||
|
|
|
@ -586,20 +586,15 @@ impl VirtualMethods for ShadowRoot {
|
||||||
|
|
||||||
shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
|
shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
|
||||||
|
|
||||||
|
let context = BindContext::new(shadow_root);
|
||||||
|
|
||||||
// avoid iterate over the shadow root itself
|
// avoid iterate over the shadow root itself
|
||||||
for node in shadow_root.traverse_preorder(ShadowIncluding::Yes).skip(1) {
|
for node in shadow_root.traverse_preorder(ShadowIncluding::Yes).skip(1) {
|
||||||
node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
|
node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
|
||||||
|
|
||||||
// Out-of-document elements never have the descendants flag set
|
// Out-of-document elements never have the descendants flag set
|
||||||
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
|
debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
|
||||||
vtable_for(&node).bind_to_tree(
|
vtable_for(&node).bind_to_tree(&context, can_gc);
|
||||||
&BindContext {
|
|
||||||
tree_connected: context.tree_connected,
|
|
||||||
tree_is_in_a_document_tree: false,
|
|
||||||
tree_is_in_a_shadow_tree: true,
|
|
||||||
},
|
|
||||||
can_gc,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,12 @@
|
||||||
[relevant-mutations.html]
|
[relevant-mutations.html]
|
||||||
[ancestor picture; previous sibling source inserted]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[picture is inserted; img has previous sibling source]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[ancestor picture; previous sibling source removed]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[crossorigin state not changed: empty to anonymous]
|
[crossorigin state not changed: empty to anonymous]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[picture is inserted; img has following sibling source]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[picture is inserted; img has src]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[crossorigin state not changed: use-credentials to USE-CREDENTIALS]
|
[crossorigin state not changed: use-credentials to USE-CREDENTIALS]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[crossorigin state not changed: anonymous to foobar]
|
[crossorigin state not changed: anonymous to foobar]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[picture is inserted; img has srcset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[sizes is set to same value]
|
[sizes is set to same value]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue