script: Move HTML DOM interfaces to script/dom/html/ (#39046)

See #38901.

Testing: Refactor
Fixes: Partially #38901

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren 2025-08-30 18:00:09 -07:00 committed by GitHub
parent ec1b9b2480
commit c92cd9e624
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 546 additions and 533 deletions

View file

@ -0,0 +1,346 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use num_traits::ToPrimitive;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use stylo_atoms::Atom;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlhyperlinkelementutils::{HyperlinkElement, HyperlinkElementTraits};
use crate::dom::html::htmlimageelement::HTMLImageElement;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::{BindContext, Node};
use crate::dom::virtualmethods::VirtualMethods;
use crate::links::{LinkRelations, follow_hyperlink};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLAnchorElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom<DOMTokenList>,
#[no_trace]
relations: Cell<LinkRelations>,
#[no_trace]
url: DomRefCell<Option<ServoUrl>>,
}
impl HTMLAnchorElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLAnchorElement {
HTMLAnchorElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
relations: Cell::new(LinkRelations::empty()),
url: DomRefCell::new(None),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLAnchorElement> {
Node::reflect_node_with_proto(
Box::new(HTMLAnchorElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HyperlinkElement for HTMLAnchorElement {
fn get_url(&self) -> &DomRefCell<Option<ServoUrl>> {
&self.url
}
}
impl VirtualMethods for HTMLAnchorElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("rel") | local_name!("rev") => {
self.relations
.set(LinkRelations::for_element(self.upcast()));
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.relations
.set(LinkRelations::for_element(self.upcast()));
}
}
impl HTMLAnchorElementMethods<crate::DomTypeHolder> for HTMLAnchorElement {
// https://html.spec.whatwg.org/multipage/#dom-a-text
fn Text(&self) -> DOMString {
self.upcast::<Node>().GetTextContent().unwrap()
}
// https://html.spec.whatwg.org/multipage/#dom-a-text
fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-a-rel
make_getter!(Rel, "rel");
// https://html.spec.whatwg.org/multipage/#dom-a-rel
fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
self.upcast::<Element>()
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
}
// https://html.spec.whatwg.org/multipage/#dom-a-rellist
fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
self.rel_list.or_init(|| {
DOMTokenList::new(
self.upcast(),
&local_name!("rel"),
Some(vec![
Atom::from("noopener"),
Atom::from("noreferrer"),
Atom::from("opener"),
]),
can_gc,
)
})
}
// https://html.spec.whatwg.org/multipage/#dom-a-coords
make_getter!(Coords, "coords");
// https://html.spec.whatwg.org/multipage/#dom-a-coords
make_setter!(SetCoords, "coords");
// https://html.spec.whatwg.org/multipage/#dom-a-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-a-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-a-rev
make_getter!(Rev, "rev");
// https://html.spec.whatwg.org/multipage/#dom-a-rev
make_setter!(SetRev, "rev");
// https://html.spec.whatwg.org/multipage/#dom-a-shape
make_getter!(Shape, "shape");
// https://html.spec.whatwg.org/multipage/#dom-a-shape
make_setter!(SetShape, "shape");
// https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
make_getter!(Target, "target");
// https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
make_setter!(SetTarget, "target");
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
fn Href(&self) -> USVString {
self.get_href()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
fn SetHref(&self, value: USVString, can_gc: CanGc) {
self.set_href(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-origin>
fn Origin(&self) -> USVString {
self.get_origin()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn Protocol(&self) -> USVString {
self.get_protocol()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn SetProtocol(&self, value: USVString, can_gc: CanGc) {
self.set_protocol(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn Password(&self) -> USVString {
self.get_password()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn SetPassword(&self, value: USVString, can_gc: CanGc) {
self.set_password(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn Hash(&self) -> USVString {
self.get_hash()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn SetHash(&self, value: USVString, can_gc: CanGc) {
self.set_hash(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn Host(&self) -> USVString {
self.get_host()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn SetHost(&self, value: USVString, can_gc: CanGc) {
self.set_host(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn Hostname(&self) -> USVString {
self.get_hostname()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn SetHostname(&self, value: USVString, can_gc: CanGc) {
self.set_hostname(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn Port(&self) -> USVString {
self.get_port()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn SetPort(&self, value: USVString, can_gc: CanGc) {
self.set_port(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn Pathname(&self) -> USVString {
self.get_pathname()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn SetPathname(&self, value: USVString, can_gc: CanGc) {
self.set_pathname(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn Search(&self) -> USVString {
self.get_search()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn SetSearch(&self, value: USVString, can_gc: CanGc) {
self.set_search(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn Username(&self) -> USVString {
self.get_username()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn SetUsername(&self, value: USVString, can_gc: CanGc) {
self.set_username(value, can_gc);
}
// https://html.spec.whatwg.org/multipage/#dom-a-referrerpolicy
fn ReferrerPolicy(&self) -> DOMString {
reflect_referrer_policy_attribute(self.upcast::<Element>())
}
// https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
make_setter!(SetReferrerPolicy, "referrerpolicy");
}
impl Activatable for HTMLAnchorElement {
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#hyperlink
// "a [...] element[s] with an href attribute [...] must [..] create a
// hyperlink"
// https://html.spec.whatwg.org/multipage/#the-a-element
// "The activation behaviour of a elements *that create hyperlinks*"
self.as_element().has_attribute(&local_name!("href"))
}
// https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour
fn activation_behavior(&self, event: &Event, target: &EventTarget, _: CanGc) {
let element = self.as_element();
let mouse_event = event.downcast::<MouseEvent>().unwrap();
let mut ismap_suffix = None;
// Step 1: If the target of the click event is an img element with an ismap attribute
// specified, then server-side image map processing must be performed.
if let Some(element) = target.downcast::<Element>() {
if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) {
let target_node = element.upcast::<Node>();
let rect = target_node.border_box().unwrap_or_default();
ismap_suffix = Some(format!(
"?{},{}",
mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px()
))
}
}
// Step 2.
// TODO: Download the link is `download` attribute is set.
follow_hyperlink(element, self.relations.get(), ismap_suffix);
}
}

View file

@ -0,0 +1,529 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::default::Default;
use std::{f32, str};
use cssparser::match_ignore_ascii_case;
use dom_struct::dom_struct;
use euclid::default::Point2D;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use stylo_atoms::Atom;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{AttributeMutation, Element, reflect_referrer_policy_attribute};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlhyperlinkelementutils::{HyperlinkElement, HyperlinkElementTraits};
use crate::dom::node::{BindContext, Node};
use crate::dom::virtualmethods::VirtualMethods;
use crate::links::{LinkRelations, follow_hyperlink};
use crate::script_runtime::CanGc;
#[derive(Debug, PartialEq)]
pub enum Area {
Circle {
left: f32,
top: f32,
radius: f32,
},
Rectangle {
top_left: (f32, f32),
bottom_right: (f32, f32),
},
Polygon {
/// Stored as a flat array of coordinates
/// e.g. [x1, y1, x2, y2, x3, y3] for a triangle
points: Vec<f32>,
},
}
pub enum Shape {
Circle,
Rectangle,
Polygon,
}
// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-list-of-floating-point-numbers
// https://html.spec.whatwg.org/multipage/#image-map-processing-model
impl Area {
pub fn parse(coord: &str, target: Shape) -> Option<Area> {
let points_count = match target {
Shape::Circle => 3,
Shape::Rectangle => 4,
Shape::Polygon => 0,
};
let size = coord.len();
let num = coord.as_bytes();
let mut index = 0;
// Step 4: Walk till char is not a delimiter
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {},
_ => break,
}
index += 1;
}
// This vector will hold all parsed coordinates
let mut number_list = Vec::new();
let mut array = Vec::new();
// Step 5: walk till end of string
while index < size {
// Step 5.1: walk till we hit a valid char i.e., 0 to 9, dot or dash, e, E
while index < size {
let val = num[index];
match val {
b'0'..=b'9' | b'.' | b'-' | b'E' | b'e' => break,
_ => {},
}
index += 1;
}
// Step 5.2: collect valid symbols till we hit another delimiter
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break,
_ => array.push(val),
}
index += 1;
}
// The input does not consist any valid characters
if array.is_empty() {
break;
}
// Convert String to float
match str::from_utf8(&array)
.ok()
.and_then(|s| s.parse::<f32>().ok())
{
Some(v) => number_list.push(v),
None => number_list.push(0.0),
};
array.clear();
// For rectangle and circle, stop parsing once we have three
// and four coordinates respectively
if points_count > 0 && number_list.len() == points_count {
break;
}
}
let final_size = number_list.len();
match target {
Shape::Circle => {
if final_size == 3 {
if number_list[2] <= 0.0 {
None
} else {
Some(Area::Circle {
left: number_list[0],
top: number_list[1],
radius: number_list[2],
})
}
} else {
None
}
},
Shape::Rectangle => {
if final_size == 4 {
if number_list[0] > number_list[2] {
number_list.swap(0, 2);
}
if number_list[1] > number_list[3] {
number_list.swap(1, 3);
}
Some(Area::Rectangle {
top_left: (number_list[0], number_list[1]),
bottom_right: (number_list[2], number_list[3]),
})
} else {
None
}
},
Shape::Polygon => {
if final_size >= 6 {
if final_size % 2 != 0 {
// Drop last element if there are odd number of coordinates
number_list.remove(final_size - 1);
}
Some(Area::Polygon {
points: number_list,
})
} else {
None
}
},
}
}
pub fn hit_test(&self, p: &Point2D<f32>) -> bool {
match *self {
Area::Circle { left, top, radius } => {
(p.x - left) * (p.x - left) + (p.y - top) * (p.y - top) - radius * radius <= 0.0
},
Area::Rectangle {
top_left,
bottom_right,
} => {
p.x <= bottom_right.0 &&
p.x >= top_left.0 &&
p.y <= bottom_right.1 &&
p.y >= top_left.1
},
Area::Polygon { ref points } => {
// Ray-casting algorithm to determine if point is inside polygon
// https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
let mut inside = false;
debug_assert!(points.len() % 2 == 0);
let vertices = points.len() / 2;
for i in 0..vertices {
let next_i = if i + 1 == vertices { 0 } else { i + 1 };
let xi = points[2 * i];
let yi = points[2 * i + 1];
let xj = points[2 * next_i];
let yj = points[2 * next_i + 1];
if (yi > p.y) != (yj > p.y) && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi {
inside = !inside;
}
}
inside
},
}
}
pub(crate) fn absolute_coords(&self, p: Point2D<f32>) -> Area {
match *self {
Area::Rectangle {
top_left,
bottom_right,
} => Area::Rectangle {
top_left: (top_left.0 + p.x, top_left.1 + p.y),
bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y),
},
Area::Circle { left, top, radius } => Area::Circle {
left: (left + p.x),
top: (top + p.y),
radius,
},
Area::Polygon { ref points } => {
// let new_points = Vec::new();
let iter = points
.iter()
.enumerate()
.map(|(index, point)| match index % 2 {
0 => point + p.x,
_ => point + p.y,
});
Area::Polygon {
points: iter.collect::<Vec<_>>(),
}
},
}
}
}
#[dom_struct]
pub(crate) struct HTMLAreaElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom<DOMTokenList>,
#[no_trace]
relations: Cell<LinkRelations>,
#[no_trace]
url: DomRefCell<Option<ServoUrl>>,
}
impl HTMLAreaElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLAreaElement {
HTMLAreaElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
relations: Cell::new(LinkRelations::empty()),
url: DomRefCell::new(None),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLAreaElement> {
Node::reflect_node_with_proto(
Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
pub(crate) fn get_shape_from_coords(&self) -> Option<Area> {
let elem = self.upcast::<Element>();
let shape = elem.get_string_attribute(&"shape".into());
let shp: Shape = match_ignore_ascii_case! { &shape,
"circle" => Shape::Circle,
"circ" => Shape::Circle,
"rectangle" => Shape::Rectangle,
"rect" => Shape::Rectangle,
"polygon" => Shape::Rectangle,
"poly" => Shape::Polygon,
_ => return None,
};
if elem.has_attribute(&"coords".into()) {
let attribute = elem.get_string_attribute(&"coords".into());
Area::parse(&attribute, shp)
} else {
None
}
}
}
impl HyperlinkElement for HTMLAreaElement {
fn get_url(&self) -> &DomRefCell<Option<ServoUrl>> {
&self.url
}
}
impl VirtualMethods for HTMLAreaElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("rel") | local_name!("rev") => {
self.relations
.set(LinkRelations::for_element(self.upcast()));
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.relations
.set(LinkRelations::for_element(self.upcast()));
}
}
impl HTMLAreaElementMethods<crate::DomTypeHolder> for HTMLAreaElement {
// https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
make_getter!(Target, "target");
// https://html.spec.whatwg.org/multipage/#attr-hyperlink-target
make_setter!(SetTarget, "target");
// https://html.spec.whatwg.org/multipage/#dom-a-rel
make_getter!(Rel, "rel");
// https://html.spec.whatwg.org/multipage/#dom-a-rel
fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
self.upcast::<Element>()
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-area-rellist>
fn RelList(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
self.rel_list.or_init(|| {
DOMTokenList::new(
self.upcast(),
&local_name!("rel"),
Some(vec![
Atom::from("noopener"),
Atom::from("noreferrer"),
Atom::from("opener"),
]),
can_gc,
)
})
}
/// <https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy>
fn ReferrerPolicy(&self) -> DOMString {
reflect_referrer_policy_attribute(self.upcast::<Element>())
}
// https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy
make_setter!(SetReferrerPolicy, "referrerpolicy");
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
fn Href(&self) -> USVString {
self.get_href()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
fn SetHref(&self, value: USVString, can_gc: CanGc) {
self.set_href(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-origin>
fn Origin(&self) -> USVString {
self.get_origin()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn Protocol(&self) -> USVString {
self.get_protocol()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn SetProtocol(&self, value: USVString, can_gc: CanGc) {
self.set_protocol(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn Password(&self) -> USVString {
self.get_password()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn SetPassword(&self, value: USVString, can_gc: CanGc) {
self.set_password(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn Hash(&self) -> USVString {
self.get_hash()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn SetHash(&self, value: USVString, can_gc: CanGc) {
self.set_hash(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn Host(&self) -> USVString {
self.get_host()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn SetHost(&self, value: USVString, can_gc: CanGc) {
self.set_host(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn Hostname(&self) -> USVString {
self.get_hostname()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn SetHostname(&self, value: USVString, can_gc: CanGc) {
self.set_hostname(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn Port(&self) -> USVString {
self.get_port()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn SetPort(&self, value: USVString, can_gc: CanGc) {
self.set_port(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn Pathname(&self) -> USVString {
self.get_pathname()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn SetPathname(&self, value: USVString, can_gc: CanGc) {
self.set_pathname(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn Search(&self) -> USVString {
self.get_search()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn SetSearch(&self, value: USVString, can_gc: CanGc) {
self.set_search(value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn Username(&self) -> USVString {
self.get_username()
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn SetUsername(&self, value: USVString, can_gc: CanGc) {
self.set_username(value, can_gc);
}
}
impl Activatable for HTMLAreaElement {
// https://html.spec.whatwg.org/multipage/#the-area-element:activation-behaviour
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
self.as_element().has_attribute(&local_name!("href"))
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
follow_hyperlink(self.as_element(), self.relations.get(), None);
}
}

View file

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, QualName, local_name, ns};
use js::rust::HandleObject;
use style::attr::AttrValue;
use crate::dom::bindings::codegen::Bindings::HTMLAudioElementBinding::HTMLAudioElementMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
use crate::dom::html::htmlmediaelement::HTMLMediaElement;
use crate::dom::node::Node;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLAudioElement {
htmlmediaelement: HTMLMediaElement,
}
impl HTMLAudioElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLAudioElement {
HTMLAudioElement {
htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLAudioElement> {
Node::reflect_node_with_proto(
Box::new(HTMLAudioElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLAudioElementMethods<crate::DomTypeHolder> for HTMLAudioElement {
// https://html.spec.whatwg.org/multipage/#dom-audio
fn Audio(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
src: Option<DOMString>,
) -> Fallible<DomRoot<HTMLAudioElement>> {
let element = Element::create(
QualName::new(None, ns!(html), local_name!("audio")),
None,
&window.Document(),
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
proto,
can_gc,
);
let audio = DomRoot::downcast::<HTMLAudioElement>(element).unwrap();
audio.upcast::<Element>().set_attribute(
&local_name!("preload"),
AttrValue::String("auto".to_owned()),
can_gc,
);
if let Some(s) = src {
audio.upcast::<Element>().set_attribute(
&local_name!("src"),
AttrValue::String(s.into()),
can_gc,
);
}
Ok(audio)
}
}

View file

@ -0,0 +1,138 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use servo_url::ServoUrl;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLBaseElementBinding::HTMLBaseElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLBaseElement {
htmlelement: HTMLElement,
}
impl HTMLBaseElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLBaseElement {
HTMLBaseElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLBaseElement> {
Node::reflect_node_with_proto(
Box::new(HTMLBaseElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
/// <https://html.spec.whatwg.org/multipage/#frozen-base-url>
pub(crate) fn frozen_base_url(&self) -> ServoUrl {
let href = self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("href"))
.expect(
"The frozen base url is only defined for base elements \
that have a base url.",
);
let document = self.owner_document();
let base = document.fallback_base_url();
let parsed = base.join(&href.value());
parsed.unwrap_or(base)
}
/// Update the cached base element in response to binding or unbinding from
/// a tree.
pub(crate) fn bind_unbind(&self, tree_in_doc: bool) {
if !tree_in_doc || self.upcast::<Node>().containing_shadow_root().is_some() {
return;
}
if self.upcast::<Element>().has_attribute(&local_name!("href")) {
let document = self.owner_document();
document.refresh_base_element();
}
}
}
impl HTMLBaseElementMethods<crate::DomTypeHolder> for HTMLBaseElement {
// https://html.spec.whatwg.org/multipage/#dom-base-href
fn Href(&self) -> DOMString {
// Step 1.
let document = self.owner_document();
// Step 2.
let attr = self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("href"));
let value = attr.as_ref().map(|attr| attr.value());
let url = value.as_ref().map_or("", |value| &**value);
// Step 3.
let url_record = document.fallback_base_url().join(url);
match url_record {
Err(_) => {
// Step 4.
url.into()
},
Ok(url_record) => {
// Step 5.
url_record.into_string().into()
},
}
}
// https://html.spec.whatwg.org/multipage/#dom-base-href
make_setter!(SetHref, "href");
}
impl VirtualMethods for HTMLBaseElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if *attr.local_name() == local_name!("href") {
self.owner_document().refresh_base_element();
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
self.super_type().unwrap().bind_to_tree(context, can_gc);
self.bind_unbind(context.tree_is_in_a_document_tree);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
self.bind_unbind(context.tree_is_in_a_document_tree);
}
}

View file

@ -0,0 +1,221 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use embedder_traits::{EmbedderMsg, LoadStatus};
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use style::color::AbsoluteColor;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLBodyElementBinding::HTMLBodyElementMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, Node, NodeTraits};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLBodyElement {
htmlelement: HTMLElement,
}
impl HTMLBodyElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLBodyElement {
HTMLBodyElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLBodyElement> {
Node::reflect_node_with_proto(
Box::new(HTMLBodyElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLBodyElementMethods<crate::DomTypeHolder> for HTMLBodyElement {
// https://html.spec.whatwg.org/multipage/#dom-body-bgcolor
make_getter!(BgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-body-bgcolor
make_legacy_color_setter!(SetBgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-body-text
make_getter!(Text, "text");
// https://html.spec.whatwg.org/multipage/#dom-body-text
make_legacy_color_setter!(SetText, "text");
// https://html.spec.whatwg.org/multipage/#dom-body-background
make_getter!(Background, "background");
// https://html.spec.whatwg.org/multipage/#dom-body-background
fn SetBackground(&self, input: DOMString, can_gc: CanGc) {
let value =
AttrValue::from_resolved_url(&self.owner_document().base_url().get_arc(), input.into());
self.upcast::<Element>()
.set_attribute(&local_name!("background"), value, can_gc);
}
// https://html.spec.whatwg.org/multipage/#windoweventhandlers
window_event_handlers!(ForwardToWindow);
}
pub(crate) trait HTMLBodyElementLayoutHelpers {
fn get_background_color(self) -> Option<AbsoluteColor>;
fn get_color(self) -> Option<AbsoluteColor>;
fn get_background(self) -> Option<ServoUrl>;
}
impl HTMLBodyElementLayoutHelpers for LayoutDom<'_, HTMLBodyElement> {
fn get_background_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("text"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_background(self) -> Option<ServoUrl> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("background"))
.and_then(AttrValue::as_resolved_url)
.cloned()
.map(Into::into)
}
}
impl VirtualMethods for HTMLBodyElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
if attr.local_name() == &local_name!("bgcolor") {
return true;
}
self.super_type()
.unwrap()
.attribute_affects_presentational_hints(attr)
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
if !context.tree_is_in_a_document_tree {
return;
}
let window = self.owner_window();
window.prevent_layout_until_load_event();
if window.is_top_level() {
window.send_to_embedder(EmbedderMsg::NotifyLoadStatusChanged(
window.webview_id(),
LoadStatus::HeadParsed,
));
}
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("bgcolor") | local_name!("text") => {
AttrValue::from_legacy_color(value.into())
},
local_name!("background") => AttrValue::from_resolved_url(
&self.owner_document().base_url().get_arc(),
value.into(),
),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
let do_super_mutate = match (attr.local_name(), mutation) {
(name, AttributeMutation::Set(_)) if name.starts_with("on") => {
let window = self.owner_window();
// https://html.spec.whatwg.org/multipage/
// #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-6
match name {
&local_name!("onafterprint") |
&local_name!("onbeforeprint") |
&local_name!("onbeforeunload") |
&local_name!("onerror") |
&local_name!("onfocus") |
&local_name!("onhashchange") |
&local_name!("onload") |
&local_name!("onlanguagechange") |
&local_name!("onmessage") |
&local_name!("onmessageerror") |
&local_name!("onoffline") |
&local_name!("ononline") |
&local_name!("onpagehide") |
&local_name!("onpagereveal") |
&local_name!("onpageshow") |
&local_name!("onpageswap") |
&local_name!("onpopstate") |
&local_name!("onrejectionhandled") |
&local_name!("onresize") |
&local_name!("onscroll") |
&local_name!("onstorage") |
&local_name!("onunhandledrejection") |
&local_name!("onunload") => {
let source = &**attr.value();
let evtarget = window.upcast::<EventTarget>(); // forwarded event
let source_line = 1; // TODO(#9604) obtain current JS execution line
evtarget.set_event_handler_uncompiled(
window.get_url(),
source_line,
&name[2..],
source,
);
false
},
_ => true, // HTMLElement::attribute_mutated will take care of this.
}
},
_ => true,
};
if do_super_mutate {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
}
}
}

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLBRElement {
htmlelement: HTMLElement,
}
impl HTMLBRElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLBRElement {
HTMLBRElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLBRElement> {
Node::reflect_node_with_proto(
Box::new(HTMLBRElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,385 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use stylo_dom::ElementState;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::html::htmlformelement::{
FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
SubmittedFrom,
};
use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
use crate::dom::nodelist::NodeList;
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
enum ButtonType {
Submit,
Reset,
Button,
}
#[dom_struct]
pub(crate) struct HTMLButtonElement {
htmlelement: HTMLElement,
button_type: Cell<ButtonType>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
validity_state: MutNullableDom<ValidityState>,
}
impl HTMLButtonElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLButtonElement {
HTMLButtonElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED,
local_name,
prefix,
document,
),
button_type: Cell::new(ButtonType::Submit),
form_owner: Default::default(),
labels_node_list: Default::default(),
validity_state: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLButtonElement> {
Node::reflect_node_with_proto(
Box::new(HTMLButtonElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
#[inline]
pub(crate) fn is_submit_button(&self) -> bool {
self.button_type.get() == ButtonType::Submit
}
}
impl HTMLButtonElementMethods<crate::DomTypeHolder> for HTMLButtonElement {
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_setter!(SetDisabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// <https://html.spec.whatwg.org/multipage/#dom-button-type>
make_enumerated_getter!(
Type,
"type",
"submit" | "reset" | "button",
missing => "submit",
invalid => "submit"
);
// https://html.spec.whatwg.org/multipage/#dom-button-type
make_setter!(SetType, "type");
// https://html.spec.whatwg.org/multipage/#dom-fs-formaction
make_form_action_getter!(FormAction, "formaction");
// https://html.spec.whatwg.org/multipage/#dom-fs-formaction
make_setter!(SetFormAction, "formaction");
// https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
make_enumerated_getter!(
FormEnctype,
"formenctype",
"application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain",
invalid => "application/x-www-form-urlencoded"
);
// https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
make_setter!(SetFormEnctype, "formenctype");
// https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
make_enumerated_getter!(
FormMethod,
"formmethod",
"get" | "post" | "dialog",
missing => "get",
invalid => "get"
);
// https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
make_setter!(SetFormMethod, "formmethod");
// https://html.spec.whatwg.org/multipage/#dom-fs-formtarget
make_getter!(FormTarget, "formtarget");
// https://html.spec.whatwg.org/multipage/#dom-fs-formtarget
make_setter!(SetFormTarget, "formtarget");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_getter!(FormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
make_bool_setter!(SetFormNoValidate, "formnovalidate");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-button-value
make_getter!(Value, "value");
// https://html.spec.whatwg.org/multipage/#dom-button-value
make_setter!(SetValue, "value");
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
}
impl HTMLButtonElement {
/// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
/// Steps range from 3.1 to 3.7 (specific to HTMLButtonElement)
pub(crate) fn form_datum(&self, submitter: Option<FormSubmitterElement>) -> Option<FormDatum> {
// Step 3.1: disabled state check is in get_unclean_dataset
// Step 3.1: only run steps if this is the submitter
if let Some(FormSubmitterElement::Button(submitter)) = submitter {
if submitter != self {
return None;
}
} else {
return None;
}
// Step 3.2
let ty = self.Type();
// Step 3.4
let name = self.Name();
if name.is_empty() {
// Step 3.1: Must have a name
return None;
}
// Step 3.9
Some(FormDatum {
ty,
name,
value: FormDatumValue::String(self.Value()),
})
}
}
impl VirtualMethods for HTMLButtonElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("disabled") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(Some(_)) => {},
AttributeMutation::Set(None) => {
el.set_disabled_state(true);
el.set_enabled_state(false);
},
AttributeMutation::Removed => {
el.set_disabled_state(false);
el.set_enabled_state(true);
el.check_ancestors_disabled_state_for_form_control();
},
}
el.update_sequentially_focusable_status(can_gc);
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
},
local_name!("type") => match mutation {
AttributeMutation::Set(_) => {
let value = match &**attr.value() {
"reset" => ButtonType::Reset,
"button" => ButtonType::Button,
_ => ButtonType::Submit,
};
self.button_type.set(value);
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
},
AttributeMutation::Removed => {
self.button_type.set(ButtonType::Submit);
},
},
local_name!("form") => {
self.form_attribute_mutated(mutation, can_gc);
self.validity_state()
.perform_validation_and_update(ValidationFlags::empty(), can_gc);
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.upcast::<Element>()
.check_ancestors_disabled_state_for_form_control();
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
if node
.ancestors()
.any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
{
el.check_ancestors_disabled_state_for_form_control();
} else {
el.check_disabled_attribute();
}
}
}
impl FormControl for HTMLButtonElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLButtonElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#the-button-element%3Abarred-from-constraint-validation
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
self.button_type.get() == ButtonType::Submit &&
!self.upcast::<Element>().disabled_state() &&
!is_barred_by_datalist_ancestor(self.upcast())
}
}
impl Activatable for HTMLButtonElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn is_instance_activatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#the-button-element
!self.upcast::<Element>().disabled_state()
}
// https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
let ty = self.button_type.get();
match ty {
// https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state
ButtonType::Submit => {
// TODO: is document owner fully active?
if let Some(owner) = self.form_owner() {
owner.submit(
SubmittedFrom::NotFromForm,
FormSubmitterElement::Button(self),
can_gc,
);
}
},
ButtonType::Reset => {
// TODO: is document owner fully active?
if let Some(owner) = self.form_owner() {
owner.reset(ResetFrom::NotFromForm, can_gc);
}
},
_ => (),
}
}
}

View file

@ -0,0 +1,678 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use canvas_traits::webgl::{GLContextAttributes, WebGLVersion};
use constellation_traits::BlobImpl;
#[cfg(feature = "webgpu")]
use constellation_traits::ScriptToConstellationMessage;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use html5ever::{LocalName, Prefix, local_name, ns};
#[cfg(feature = "webgpu")]
use ipc_channel::ipc::{self as ipcchan};
use js::error::throw_type_error;
use js::rust::{HandleObject, HandleValue};
use layout_api::HTMLCanvasData;
use pixels::{EncodedImageType, Snapshot};
use script_bindings::weakref::WeakRef;
use servo_media::streams::MediaStreamType;
use servo_media::streams::registry::MediaStreamId;
use style::attr::AttrValue;
pub(crate) use crate::canvas_context::*;
use crate::conversions::Convert;
use crate::dom::attr::Attr;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{
BlobCallback, HTMLCanvasElementMethods, RenderingContext as RootedRenderingContext,
};
use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::conversions::ConversionResult;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, ToLayout};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::blob::Blob;
use crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
#[cfg(not(feature = "webgpu"))]
use crate::dom::gpucanvascontext::GPUCanvasContext;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::imagebitmaprenderingcontext::ImageBitmapRenderingContext;
use crate::dom::mediastream::MediaStream;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::node::{Node, NodeDamage, NodeTraits};
use crate::dom::offscreencanvas::OffscreenCanvas;
use crate::dom::values::UNSIGNED_LONG_MAX;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::webgl::webgl2renderingcontext::WebGL2RenderingContext;
use crate::dom::webgl::webglrenderingcontext::WebGLRenderingContext;
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
use crate::script_runtime::{CanGc, JSContext};
const DEFAULT_WIDTH: u32 = 300;
const DEFAULT_HEIGHT: u32 = 150;
/// <https://html.spec.whatwg.org/multipage/#htmlcanvaselement>
#[dom_struct]
pub(crate) struct HTMLCanvasElement {
htmlelement: HTMLElement,
/// <https://html.spec.whatwg.org/multipage/#concept-canvas-context-mode>
context_mode: DomRefCell<Option<RenderingContext>>,
// This id and hashmap are used to keep track of ongoing toBlob() calls.
callback_id: Cell<u32>,
#[ignore_malloc_size_of = "not implemented for webidl callbacks"]
blob_callbacks: RefCell<HashMap<u32, Rc<BlobCallback>>>,
}
impl HTMLCanvasElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLCanvasElement {
HTMLCanvasElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
context_mode: DomRefCell::new(None),
callback_id: Cell::new(0),
blob_callbacks: RefCell::new(HashMap::new()),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLCanvasElement> {
Node::reflect_node_with_proto(
Box::new(HTMLCanvasElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn recreate_contexts_after_resize(&self) {
if let Some(ref context) = *self.context_mode.borrow() {
context.resize()
}
}
pub(crate) fn get_size(&self) -> Size2D<u32> {
Size2D::new(self.Width(), self.Height())
}
pub(crate) fn origin_is_clean(&self) -> bool {
match *self.context_mode.borrow() {
Some(ref context) => context.origin_is_clean(),
_ => true,
}
}
pub(crate) fn mark_as_dirty(&self) {
if let Some(ref context) = *self.context_mode.borrow() {
context.mark_as_dirty()
}
}
pub(crate) fn set_natural_width(&self, value: u32, can_gc: CanGc) {
let value = if value > UNSIGNED_LONG_MAX {
DEFAULT_WIDTH
} else {
value
};
let element = self.upcast::<Element>();
element.set_uint_attribute(&html5ever::local_name!("width"), value, can_gc);
}
pub(crate) fn set_natural_height(&self, value: u32, can_gc: CanGc) {
let value = if value > UNSIGNED_LONG_MAX {
DEFAULT_HEIGHT
} else {
value
};
let element = self.upcast::<Element>();
element.set_uint_attribute(&html5ever::local_name!("height"), value, can_gc);
}
}
impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> {
#[allow(unsafe_code)]
fn data(self) -> HTMLCanvasData {
let source = unsafe {
match self.unsafe_get().context_mode.borrow_for_layout().as_ref() {
Some(RenderingContext::Context2d(context)) => {
context.to_layout().canvas_data_source()
},
Some(RenderingContext::BitmapRenderer(context)) => {
context.to_layout().canvas_data_source()
},
Some(RenderingContext::WebGL(context)) => context.to_layout().canvas_data_source(),
Some(RenderingContext::WebGL2(context)) => context.to_layout().canvas_data_source(),
#[cfg(feature = "webgpu")]
Some(RenderingContext::WebGPU(context)) => context.to_layout().canvas_data_source(),
Some(RenderingContext::Placeholder(_)) | None => None,
}
};
let width_attr = self
.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"));
let height_attr = self
.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"));
HTMLCanvasData {
source,
width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
}
}
}
impl HTMLCanvasElement {
pub(crate) fn context(&self) -> Option<Ref<'_, RenderingContext>> {
Ref::filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()).ok()
}
fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option<DomRoot<CanvasRenderingContext2D>> {
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
let window = self.owner_window();
let size = self.get_size();
let context = CanvasRenderingContext2D::new(window.as_global_scope(), self, size, can_gc)?;
*self.context_mode.borrow_mut() =
Some(RenderingContext::Context2d(Dom::from_ref(&*context)));
Some(context)
}
/// <https://html.spec.whatwg.org/multipage/#canvas-context-bitmaprenderer>
fn get_or_init_bitmaprenderer_context(
&self,
can_gc: CanGc,
) -> Option<DomRoot<ImageBitmapRenderingContext>> {
// Return the same object as was returned the last time the method was
// invoked with this same first argument.
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::BitmapRenderer(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
// Step 1. Let context be the result of running the
// ImageBitmapRenderingContext creation algorithm given this and
// options.
let context = ImageBitmapRenderingContext::new(
&self.owner_global(),
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self)),
can_gc,
);
// Step 2. Set this's context mode to bitmaprenderer.
*self.context_mode.borrow_mut() =
Some(RenderingContext::BitmapRenderer(Dom::from_ref(&*context)));
// Step 3. Return context.
Some(context)
}
fn get_or_init_webgl_context(
&self,
cx: JSContext,
options: HandleValue,
can_gc: CanGc,
) -> Option<DomRoot<WebGLRenderingContext>> {
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
let window = self.owner_window();
let size = self.get_size();
let attrs = Self::get_gl_attributes(cx, options)?;
let canvas = HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
let context = WebGLRenderingContext::new(
&window,
&canvas,
WebGLVersion::WebGL1,
size,
attrs,
can_gc,
)?;
*self.context_mode.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context)));
Some(context)
}
fn get_or_init_webgl2_context(
&self,
cx: JSContext,
options: HandleValue,
can_gc: CanGc,
) -> Option<DomRoot<WebGL2RenderingContext>> {
if !WebGL2RenderingContext::is_webgl2_enabled(cx, self.global().reflector().get_jsobject())
{
return None;
}
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
let window = self.owner_window();
let size = self.get_size();
let attrs = Self::get_gl_attributes(cx, options)?;
let canvas = HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self));
let context = WebGL2RenderingContext::new(&window, &canvas, size, attrs, can_gc)?;
*self.context_mode.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context)));
Some(context)
}
#[cfg(not(feature = "webgpu"))]
fn get_or_init_webgpu_context(&self) -> Option<DomRoot<GPUCanvasContext>> {
None
}
#[cfg(feature = "webgpu")]
fn get_or_init_webgpu_context(&self, can_gc: CanGc) -> Option<DomRoot<GPUCanvasContext>> {
if let Some(ctx) = self.context() {
return match *ctx {
RenderingContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)),
_ => None,
};
}
let (sender, receiver) = ipcchan::channel().unwrap();
let global_scope = self.owner_global();
let _ = global_scope
.script_to_constellation_chan()
.send(ScriptToConstellationMessage::GetWebGPUChan(sender));
receiver
.recv()
.expect("Failed to get WebGPU channel")
.map(|channel| {
let context = GPUCanvasContext::new(&global_scope, self, channel, can_gc);
*self.context_mode.borrow_mut() =
Some(RenderingContext::WebGPU(Dom::from_ref(&*context)));
context
})
}
/// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists.
pub(crate) fn get_base_webgl_context(&self) -> Option<DomRoot<WebGLRenderingContext>> {
match *self.context_mode.borrow() {
Some(RenderingContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)),
Some(RenderingContext::WebGL2(ref context)) => Some(context.base_context()),
_ => None,
}
}
#[allow(unsafe_code)]
fn get_gl_attributes(cx: JSContext, options: HandleValue) -> Option<GLContextAttributes> {
unsafe {
match WebGLContextAttributes::new(cx, options) {
Ok(ConversionResult::Success(attrs)) => Some(attrs.convert()),
Ok(ConversionResult::Failure(error)) => {
throw_type_error(*cx, &error);
None
},
_ => {
debug!("Unexpected error on conversion of WebGLContextAttributes");
None
},
}
}
}
pub(crate) fn is_valid(&self) -> bool {
self.Height() != 0 && self.Width() != 0
}
pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
match self.context_mode.borrow().as_ref() {
Some(context) => context.get_image_data(),
None => {
let size = self.get_size();
if size.is_empty() ||
pixels::compute_rgba8_byte_length_if_within_limit(
size.width as usize,
size.height as usize,
)
.is_none()
{
None
} else {
Some(Snapshot::cleared(size.cast()))
}
},
}
}
fn maybe_quality(quality: HandleValue) -> Option<f64> {
if quality.is_number() {
Some(quality.to_number())
} else {
None
}
}
}
impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
// https://html.spec.whatwg.org/multipage/#dom-canvas-width
make_uint_getter!(Width, "width", DEFAULT_WIDTH);
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-width>
fn SetWidth(&self, value: u32, can_gc: CanGc) -> Fallible<()> {
// > When setting the value of the width or height attribute, if the context mode of the canvas element
// > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the
// > attribute's value unchanged.
if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
return Err(Error::InvalidState);
}
let value = if value > UNSIGNED_LONG_MAX {
DEFAULT_WIDTH
} else {
value
};
let element = self.upcast::<Element>();
element.set_uint_attribute(&html5ever::local_name!("width"), value, can_gc);
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-canvas-height
make_uint_getter!(Height, "height", DEFAULT_HEIGHT);
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-height>
fn SetHeight(&self, value: u32, can_gc: CanGc) -> Fallible<()> {
// > When setting the value of the width or height attribute, if the context mode of the canvas element
// > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the
// > attribute's value unchanged.
if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
return Err(Error::InvalidState);
}
let value = if value > UNSIGNED_LONG_MAX {
DEFAULT_HEIGHT
} else {
value
};
let element = self.upcast::<Element>();
element.set_uint_attribute(&html5ever::local_name!("height"), value, can_gc);
Ok(())
}
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext>
fn GetContext(
&self,
cx: JSContext,
id: DOMString,
options: HandleValue,
can_gc: CanGc,
) -> Fallible<Option<RootedRenderingContext>> {
// Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec).
if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() {
return Err(Error::InvalidState);
}
Ok(match &*id {
"2d" => self
.get_or_init_2d_context(can_gc)
.map(RootedRenderingContext::CanvasRenderingContext2D),
"bitmaprenderer" => self
.get_or_init_bitmaprenderer_context(can_gc)
.map(RootedRenderingContext::ImageBitmapRenderingContext),
"webgl" | "experimental-webgl" => self
.get_or_init_webgl_context(cx, options, can_gc)
.map(RootedRenderingContext::WebGLRenderingContext),
"webgl2" | "experimental-webgl2" => self
.get_or_init_webgl2_context(cx, options, can_gc)
.map(RootedRenderingContext::WebGL2RenderingContext),
#[cfg(feature = "webgpu")]
"webgpu" => self
.get_or_init_webgpu_context(can_gc)
.map(RootedRenderingContext::GPUCanvasContext),
_ => None,
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-todataurl>
fn ToDataURL(
&self,
_context: JSContext,
mime_type: DOMString,
quality: HandleValue,
) -> Fallible<USVString> {
// Step 1: If this canvas element's bitmap's origin-clean flag is set to false,
// then throw a "SecurityError" DOMException.
if !self.origin_is_clean() {
return Err(Error::Security);
}
// Step 2: If this canvas element's bitmap has no pixels (i.e. either its
// horizontal dimension or its vertical dimension is zero), then return the string
// "data:,". (This is the shortest data: URL; it represents the empty string in a
// text/plain resource.)
if self.Width() == 0 || self.Height() == 0 {
return Ok(USVString("data:,".into()));
}
// Step 3: Let file be a serialization of this canvas element's bitmap as a file,
// passing type and quality if given.
let Some(mut snapshot) = self.get_image_data() else {
return Ok(USVString("data:,".into()));
};
let image_type = EncodedImageType::from(mime_type.to_string());
let mut url = format!("data:{};base64,", image_type.as_mime_type());
let mut encoder = base64::write::EncoderStringWriter::from_consumer(
&mut url,
&base64::engine::general_purpose::STANDARD,
);
if snapshot
.encode_for_mime_type(&image_type, Self::maybe_quality(quality), &mut encoder)
.is_err()
{
// Step 4. If file is null, then return "data:,".
return Ok(USVString("data:,".into()));
}
// Step 5. Return a data: URL representing file. [RFC2397]
encoder.into_inner();
Ok(USVString(url))
}
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-toblob>
fn ToBlob(
&self,
_cx: JSContext,
callback: Rc<BlobCallback>,
mime_type: DOMString,
quality: HandleValue,
) -> Fallible<()> {
// Step 1.
// If this canvas element's bitmap's origin-clean flag is set to false, then throw a
// "SecurityError" DOMException.
if !self.origin_is_clean() {
return Err(Error::Security);
}
// Step 2. Let result be null.
// Step 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension
// nor its vertical dimension is zero),
// then set result to a copy of this canvas element's bitmap.
let result = if self.Width() == 0 || self.Height() == 0 {
None
} else {
self.get_image_data()
};
let this = Trusted::new(self);
let callback_id = self.callback_id.get().wrapping_add(1);
self.callback_id.set(callback_id);
self.blob_callbacks
.borrow_mut()
.insert(callback_id, callback);
let quality = Self::maybe_quality(quality);
let image_type = EncodedImageType::from(mime_type.to_string());
self.global()
.task_manager()
.canvas_blob_task_source()
.queue(task!(to_blob: move || {
let this = this.root();
let Some(callback) = &this.blob_callbacks.borrow_mut().remove(&callback_id) else {
return error!("Expected blob callback, but found none!");
};
let Some(mut snapshot) = result else {
let _ = callback.Call__(None, ExceptionHandling::Report, CanGc::note());
return;
};
// Step 4.1: If result is non-null, then set result to a serialization of
// result as a file with type and quality if given.
// Step 4.2: Queue an element task on the canvas blob serialization task
// source given the canvas element to run these steps:
let mut encoded: Vec<u8> = vec![];
let blob_impl;
let blob;
let result = match snapshot.encode_for_mime_type(&image_type, quality, &mut encoded) {
Ok(..) => {
// Step 4.2.1: If result is non-null, then set result to a new Blob
// object, created in the relevant realm of this canvas element,
// representing result. [FILEAPI]
blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
blob = Blob::new(&this.global(), blob_impl, CanGc::note());
Some(&*blob)
}
Err(..) => None,
};
// Step 4.2.2: Invoke callback with « result » and "report".
let _ = callback.Call__(result, ExceptionHandling::Report, CanGc::note());
}));
Ok(())
}
/// <https://html.spec.whatwg.org/multipage/#dom-canvas-transfercontroltooffscreen>
fn TransferControlToOffscreen(&self, can_gc: CanGc) -> Fallible<DomRoot<OffscreenCanvas>> {
if self.context_mode.borrow().is_some() {
// Step 1.
// If this canvas element's context mode is not set to none, throw an "InvalidStateError" DOMException.
return Err(Error::InvalidState);
};
// Step 2.
// Let offscreenCanvas be a new OffscreenCanvas object with its width and height equal to the values of
// the width and height content attributes of this canvas element.
// Step 3.
// Set the placeholder canvas element of offscreenCanvas to a weak reference to this canvas element.
let offscreen_canvas = OffscreenCanvas::new(
&self.global(),
None,
self.Width().into(),
self.Height().into(),
Some(WeakRef::new(self)),
can_gc,
);
// Step 4. Set this canvas element's context mode to placeholder.
*self.context_mode.borrow_mut() =
Some(RenderingContext::Placeholder(offscreen_canvas.as_traced()));
// Step 5. Return offscreenCanvas.
Ok(offscreen_canvas)
}
/// <https://w3c.github.io/mediacapture-fromelement/#dom-htmlcanvaselement-capturestream>
fn CaptureStream(
&self,
_frame_request_rate: Option<Finite<f64>>,
can_gc: CanGc,
) -> DomRoot<MediaStream> {
let global = self.global();
let stream = MediaStream::new(&global, can_gc);
let track = MediaStreamTrack::new(
&global,
MediaStreamId::new(),
MediaStreamType::Video,
can_gc,
);
stream.AddTrack(&track);
stream
}
}
impl VirtualMethods for HTMLCanvasElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match attr.local_name() {
&local_name!("width") | &local_name!("height") => {
self.recreate_contexts_after_resize();
self.upcast::<Node>().dirty(NodeDamage::Other);
},
_ => {},
};
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH),
local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}
impl Convert<GLContextAttributes> for WebGLContextAttributes {
fn convert(self) -> GLContextAttributes {
GLContextAttributes {
alpha: self.alpha,
depth: self.depth,
stencil: self.stencil,
antialias: self.antialias,
premultiplied_alpha: self.premultipliedAlpha,
preserve_drawing_buffer: self.preserveDrawingBuffer,
}
}
}

View file

@ -0,0 +1,461 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::cmp::Ordering;
use dom_struct::dom_struct;
use html5ever::{LocalName, QualName, local_name, namespace_url, ns};
use style::str::split_html_space_chars;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use crate::dom::bindings::domname::namespace_from_domstring;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::JSTraceable;
use crate::dom::element::Element;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
pub(crate) trait CollectionFilter: JSTraceable {
fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool;
}
/// An optional `u32`, using `u32::MAX` to represent None. It would be nicer
/// just to use `Option<u32>` for this, but that would produce word alignment
/// issues since `Option<u32>` uses 33 bits.
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
struct OptionU32 {
bits: u32,
}
impl OptionU32 {
fn to_option(self) -> Option<u32> {
if self.bits == u32::MAX {
None
} else {
Some(self.bits)
}
}
fn some(bits: u32) -> OptionU32 {
assert_ne!(bits, u32::MAX);
OptionU32 { bits }
}
fn none() -> OptionU32 {
OptionU32 { bits: u32::MAX }
}
}
#[dom_struct]
pub(crate) struct HTMLCollection {
reflector_: Reflector,
root: Dom<Node>,
#[ignore_malloc_size_of = "Trait object (Box<dyn CollectionFilter>) cannot be sized"]
filter: Box<dyn CollectionFilter + 'static>,
// We cache the version of the root node and all its decendents,
// the length of the collection, and a cursor into the collection.
// FIXME: make the cached cursor element a weak pointer
cached_version: Cell<u64>,
cached_cursor_element: MutNullableDom<Element>,
cached_cursor_index: Cell<OptionU32>,
cached_length: Cell<OptionU32>,
}
impl HTMLCollection {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new_inherited(
root: &Node,
filter: Box<dyn CollectionFilter + 'static>,
) -> HTMLCollection {
HTMLCollection {
reflector_: Reflector::new(),
root: Dom::from_ref(root),
filter,
// Default values for the cache
cached_version: Cell::new(root.inclusive_descendants_version()),
cached_cursor_element: MutNullableDom::new(None),
cached_cursor_index: Cell::new(OptionU32::none()),
cached_length: Cell::new(OptionU32::none()),
}
}
/// Returns a collection which is always empty.
pub(crate) fn always_empty(window: &Window, root: &Node, can_gc: CanGc) -> DomRoot<Self> {
#[derive(JSTraceable)]
struct NoFilter;
impl CollectionFilter for NoFilter {
fn filter<'a>(&self, _: &'a Element, _: &'a Node) -> bool {
false
}
}
Self::new(window, root, Box::new(NoFilter), can_gc)
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
window: &Window,
root: &Node,
filter: Box<dyn CollectionFilter + 'static>,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(Box::new(Self::new_inherited(root, filter)), window, can_gc)
}
/// Create a new [`HTMLCollection`] that just filters element using a static function.
pub(crate) fn new_with_filter_fn(
window: &Window,
root: &Node,
filter_function: fn(&Element, &Node) -> bool,
can_gc: CanGc,
) -> DomRoot<Self> {
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct StaticFunctionFilter(
// The function *must* be static so that it never holds references to DOM objects, which
// would cause issues with garbage collection -- since it isn't traced.
#[no_trace]
#[ignore_malloc_size_of = "Static function pointer"]
fn(&Element, &Node) -> bool,
);
impl CollectionFilter for StaticFunctionFilter {
fn filter(&self, element: &Element, root: &Node) -> bool {
(self.0)(element, root)
}
}
Self::new(
window,
root,
Box::new(StaticFunctionFilter(filter_function)),
can_gc,
)
}
pub(crate) fn create(
window: &Window,
root: &Node,
filter: Box<dyn CollectionFilter + 'static>,
can_gc: CanGc,
) -> DomRoot<Self> {
Self::new(window, root, filter, can_gc)
}
fn validate_cache(&self) {
// Clear the cache if the root version is different from our cached version
let cached_version = self.cached_version.get();
let curr_version = self.root.inclusive_descendants_version();
if curr_version != cached_version {
// Default values for the cache
self.cached_version.set(curr_version);
self.cached_cursor_element.set(None);
self.cached_length.set(OptionU32::none());
self.cached_cursor_index.set(OptionU32::none());
}
}
fn set_cached_cursor(
&self,
index: u32,
element: Option<DomRoot<Element>>,
) -> Option<DomRoot<Element>> {
if let Some(element) = element {
self.cached_cursor_index.set(OptionU32::some(index));
self.cached_cursor_element.set(Some(&element));
Some(element)
} else {
None
}
}
/// <https://dom.spec.whatwg.org/#concept-getelementsbytagname>
pub(crate) fn by_qualified_name(
window: &Window,
root: &Node,
qualified_name: LocalName,
can_gc: CanGc,
) -> DomRoot<HTMLCollection> {
// case 1
if qualified_name == local_name!("*") {
#[derive(JSTraceable, MallocSizeOf)]
struct AllFilter;
impl CollectionFilter for AllFilter {
fn filter(&self, _elem: &Element, _root: &Node) -> bool {
true
}
}
return HTMLCollection::create(window, root, Box::new(AllFilter), can_gc);
}
#[derive(JSTraceable, MallocSizeOf)]
struct HtmlDocumentFilter {
#[no_trace]
qualified_name: LocalName,
#[no_trace]
ascii_lower_qualified_name: LocalName,
}
impl CollectionFilter for HtmlDocumentFilter {
fn filter(&self, elem: &Element, root: &Node) -> bool {
if root.is_in_html_doc() && elem.namespace() == &ns!(html) {
// case 2
HTMLCollection::match_element(elem, &self.ascii_lower_qualified_name)
} else {
// case 2 and 3
HTMLCollection::match_element(elem, &self.qualified_name)
}
}
}
let filter = HtmlDocumentFilter {
ascii_lower_qualified_name: qualified_name.to_ascii_lowercase(),
qualified_name,
};
HTMLCollection::create(window, root, Box::new(filter), can_gc)
}
fn match_element(elem: &Element, qualified_name: &LocalName) -> bool {
match elem.prefix().as_ref() {
None => elem.local_name() == qualified_name,
Some(prefix) => {
qualified_name.starts_with(&**prefix) &&
qualified_name.find(':') == Some(prefix.len()) &&
qualified_name.ends_with(&**elem.local_name())
},
}
}
pub(crate) fn by_tag_name_ns(
window: &Window,
root: &Node,
tag: DOMString,
maybe_ns: Option<DOMString>,
can_gc: CanGc,
) -> DomRoot<HTMLCollection> {
let local = LocalName::from(tag);
let ns = namespace_from_domstring(maybe_ns);
let qname = QualName::new(None, ns, local);
HTMLCollection::by_qual_tag_name(window, root, qname, can_gc)
}
pub(crate) fn by_qual_tag_name(
window: &Window,
root: &Node,
qname: QualName,
can_gc: CanGc,
) -> DomRoot<HTMLCollection> {
#[derive(JSTraceable, MallocSizeOf)]
struct TagNameNSFilter {
#[no_trace]
qname: QualName,
}
impl CollectionFilter for TagNameNSFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
((self.qname.ns == namespace_url!("*")) || (self.qname.ns == *elem.namespace())) &&
((self.qname.local == local_name!("*")) ||
(self.qname.local == *elem.local_name()))
}
}
let filter = TagNameNSFilter { qname };
HTMLCollection::create(window, root, Box::new(filter), can_gc)
}
pub(crate) fn by_class_name(
window: &Window,
root: &Node,
classes: DOMString,
can_gc: CanGc,
) -> DomRoot<HTMLCollection> {
let class_atoms = split_html_space_chars(&classes).map(Atom::from).collect();
HTMLCollection::by_atomic_class_name(window, root, class_atoms, can_gc)
}
pub(crate) fn by_atomic_class_name(
window: &Window,
root: &Node,
classes: Vec<Atom>,
can_gc: CanGc,
) -> DomRoot<HTMLCollection> {
#[derive(JSTraceable, MallocSizeOf)]
struct ClassNameFilter {
#[no_trace]
classes: Vec<Atom>,
}
impl CollectionFilter for ClassNameFilter {
fn filter(&self, elem: &Element, _root: &Node) -> bool {
let case_sensitivity = elem
.owner_document()
.quirks_mode()
.classes_and_ids_case_sensitivity();
self.classes
.iter()
.all(|class| elem.has_class(class, case_sensitivity))
}
}
if classes.is_empty() {
return HTMLCollection::always_empty(window, root, can_gc);
}
let filter = ClassNameFilter { classes };
HTMLCollection::create(window, root, Box::new(filter), can_gc)
}
pub(crate) fn children(window: &Window, root: &Node, can_gc: CanGc) -> DomRoot<HTMLCollection> {
HTMLCollection::new_with_filter_fn(
window,
root,
|element, root| root.is_parent_of(element.upcast()),
can_gc,
)
}
pub(crate) fn elements_iter_after<'a>(
&'a self,
after: &'a Node,
) -> impl Iterator<Item = DomRoot<Element>> + 'a {
// Iterate forwards from a node.
after
.following_nodes(&self.root)
.filter_map(DomRoot::downcast)
.filter(move |element| self.filter.filter(element, &self.root))
}
pub(crate) fn elements_iter(&self) -> impl Iterator<Item = DomRoot<Element>> + '_ {
// Iterate forwards from the root.
self.elements_iter_after(&self.root)
}
pub(crate) fn elements_iter_before<'a>(
&'a self,
before: &'a Node,
) -> impl Iterator<Item = DomRoot<Element>> + 'a {
// Iterate backwards from a node.
before
.preceding_nodes(&self.root)
.filter_map(DomRoot::downcast)
.filter(move |element| self.filter.filter(element, &self.root))
}
pub(crate) fn root_node(&self) -> DomRoot<Node> {
DomRoot::from_ref(&self.root)
}
}
impl HTMLCollectionMethods<crate::DomTypeHolder> for HTMLCollection {
/// <https://dom.spec.whatwg.org/#dom-htmlcollection-length>
fn Length(&self) -> u32 {
self.validate_cache();
if let Some(cached_length) = self.cached_length.get().to_option() {
// Cache hit
cached_length
} else {
// Cache miss, calculate the length
let length = self.elements_iter().count() as u32;
self.cached_length.set(OptionU32::some(length));
length
}
}
/// <https://dom.spec.whatwg.org/#dom-htmlcollection-item>
fn Item(&self, index: u32) -> Option<DomRoot<Element>> {
self.validate_cache();
if let Some(element) = self.cached_cursor_element.get() {
// Cache hit, the cursor element is set
if let Some(cached_index) = self.cached_cursor_index.get().to_option() {
match cached_index.cmp(&index) {
Ordering::Equal => {
// The cursor is the element we're looking for
Some(element)
},
Ordering::Less => {
// The cursor is before the element we're looking for
// Iterate forwards, starting at the cursor.
let offset = index - (cached_index + 1);
let node: DomRoot<Node> = DomRoot::upcast(element);
let mut iter = self.elements_iter_after(&node);
self.set_cached_cursor(index, iter.nth(offset as usize))
},
Ordering::Greater => {
// The cursor is after the element we're looking for
// Iterate backwards, starting at the cursor.
let offset = cached_index - (index + 1);
let node: DomRoot<Node> = DomRoot::upcast(element);
let mut iter = self.elements_iter_before(&node);
self.set_cached_cursor(index, iter.nth(offset as usize))
},
}
} else {
// Cache miss
// Iterate forwards through all the nodes
self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
}
} else {
// Cache miss
// Iterate forwards through all the nodes
self.set_cached_cursor(index, self.elements_iter().nth(index as usize))
}
}
/// <https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem>
fn NamedItem(&self, key: DOMString) -> Option<DomRoot<Element>> {
// Step 1.
if key.is_empty() {
return None;
}
let key = Atom::from(key);
// Step 2.
self.elements_iter().find(|elem| {
elem.get_id().is_some_and(|id| id == key) ||
(elem.namespace() == &ns!(html) && elem.get_name().is_some_and(|id| id == key))
})
}
// https://dom.spec.whatwg.org/#dom-htmlcollection-item
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
self.Item(index)
}
// check-tidy: no specs after this line
fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> {
self.NamedItem(name)
}
// https://dom.spec.whatwg.org/#interface-htmlcollection
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
// Step 1
let mut result = vec![];
// Step 2
for elem in self.elements_iter() {
// Step 2.1
if let Some(id_atom) = elem.get_id() {
let id_str = DOMString::from(&*id_atom);
if !result.contains(&id_str) {
result.push(id_str);
}
}
// Step 2.2
if *elem.namespace() == ns!(html) {
if let Some(name_atom) = elem.get_name() {
let name_str = DOMString::from(&*name_atom);
if !result.contains(&name_str) {
result.push(name_str)
}
}
}
}
// Step 3
result
}
}

View file

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLDataElementBinding::HTMLDataElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDataElement {
htmlelement: HTMLElement,
}
impl HTMLDataElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDataElement {
HTMLDataElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDataElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDataElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLDataElementMethods<crate::DomTypeHolder> for HTMLDataElement {
// https://html.spec.whatwg.org/multipage/#dom-data-value
make_getter!(Value, "value");
// https://html.spec.whatwg.org/multipage/#dom-data-value
make_setter!(SetValue, "value");
}

View file

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLDataListElementBinding::HTMLDataListElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlcollection::HTMLCollection;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmloptionelement::HTMLOptionElement;
use crate::dom::node::{Node, NodeTraits};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDataListElement {
htmlelement: HTMLElement,
}
impl HTMLDataListElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDataListElement {
HTMLDataListElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDataListElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDataListElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLDataListElementMethods<crate::DomTypeHolder> for HTMLDataListElement {
// https://html.spec.whatwg.org/multipage/#dom-datalist-options
fn Options(&self, can_gc: CanGc) -> DomRoot<HTMLCollection> {
HTMLCollection::new_with_filter_fn(
&self.owner_window(),
self.upcast(),
|element, _| element.is::<HTMLOptionElement>(),
can_gc,
)
}
}

View file

@ -0,0 +1,257 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, Ref};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLDetailsElementBinding::HTMLDetailsElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlslotelement::HTMLSlotElement;
use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeDamage, NodeTraits};
use crate::dom::text::Text;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
/// The summary that should be presented if no `<summary>` element is present
const DEFAULT_SUMMARY: &str = "Details";
/// Holds handles to all slots in the UA shadow tree
///
/// The composition of the tree is described in
/// <https://html.spec.whatwg.org/multipage/#the-details-and-summary-elements>
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ShadowTree {
summary: Dom<HTMLSlotElement>,
descendants: Dom<HTMLSlotElement>,
/// The summary that is displayed if no other summary exists
implicit_summary: Dom<HTMLElement>,
}
#[dom_struct]
pub(crate) struct HTMLDetailsElement {
htmlelement: HTMLElement,
toggle_counter: Cell<u32>,
/// Represents the UA widget for the details element
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
impl HTMLDetailsElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDetailsElement {
HTMLDetailsElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
toggle_counter: Cell::new(0),
shadow_tree: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDetailsElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDetailsElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn toggle(&self) {
self.SetOpen(!self.Open());
}
fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
if !self.upcast::<Element>().is_shadow_host() {
self.create_shadow_tree(can_gc);
}
Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
.ok()
.expect("UA shadow tree was not created")
}
fn create_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
// TODO(stevennovaryo): Reimplement details styling so that it would not
// mess the cascading and require some reparsing.
let root = self
.upcast::<Element>()
.attach_ua_shadow_root(false, can_gc);
let summary = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
root.upcast::<Node>()
.AppendChild(summary.upcast::<Node>(), can_gc)
.unwrap();
let fallback_summary =
HTMLElement::new(local_name!("summary"), None, &document, None, can_gc);
fallback_summary
.upcast::<Node>()
.set_text_content_for_element(Some(DEFAULT_SUMMARY.into()), can_gc);
summary
.upcast::<Node>()
.AppendChild(fallback_summary.upcast::<Node>(), can_gc)
.unwrap();
let descendants = HTMLSlotElement::new(local_name!("slot"), None, &document, None, can_gc);
root.upcast::<Node>()
.AppendChild(descendants.upcast::<Node>(), can_gc)
.unwrap();
let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
summary: summary.as_traced(),
descendants: descendants.as_traced(),
implicit_summary: fallback_summary.as_traced(),
});
self.upcast::<Node>()
.dirty(crate::dom::node::NodeDamage::Other);
}
pub(crate) fn find_corresponding_summary_element(&self) -> Option<DomRoot<HTMLElement>> {
self.upcast::<Node>()
.children()
.filter_map(DomRoot::downcast::<HTMLElement>)
.find(|html_element| {
html_element.upcast::<Element>().local_name() == &local_name!("summary")
})
}
fn update_shadow_tree_contents(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
if let Some(summary) = self.find_corresponding_summary_element() {
shadow_tree
.summary
.Assign(vec![ElementOrText::Element(DomRoot::upcast(summary))]);
}
let mut slottable_children = vec![];
for child in self.upcast::<Node>().children() {
if let Some(element) = child.downcast::<Element>() {
if element.local_name() == &local_name!("summary") {
continue;
}
slottable_children.push(ElementOrText::Element(DomRoot::from_ref(element)));
}
if let Some(text) = child.downcast::<Text>() {
slottable_children.push(ElementOrText::Text(DomRoot::from_ref(text)));
}
}
shadow_tree.descendants.Assign(slottable_children);
}
fn update_shadow_tree_styles(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
let value = if self.Open() {
"display: block;"
} else {
// TODO: This should be "display: block; content-visibility: hidden;",
// but servo does not support content-visibility yet
"display: none;"
};
shadow_tree
.descendants
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), value.into(), can_gc);
// Manually update the list item style of the implicit summary element.
// Unlike the other summaries, this summary is in the shadow tree and
// can't be styled with UA sheets
let implicit_summary_list_item_style = if self.Open() {
"disclosure-open"
} else {
"disclosure-closed"
};
let implicit_summary_style = format!(
"display: list-item;
counter-increment: list-item 0;
list-style: {implicit_summary_list_item_style} inside;"
);
shadow_tree
.implicit_summary
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc);
}
}
impl HTMLDetailsElementMethods<crate::DomTypeHolder> for HTMLDetailsElement {
// https://html.spec.whatwg.org/multipage/#dom-details-open
make_bool_getter!(Open, "open");
// https://html.spec.whatwg.org/multipage/#dom-details-open
make_bool_setter!(SetOpen, "open");
}
impl VirtualMethods for HTMLDetailsElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if attr.local_name() == &local_name!("open") {
self.update_shadow_tree_styles(can_gc);
let counter = self.toggle_counter.get() + 1;
self.toggle_counter.set(counter);
let this = Trusted::new(self);
self.owner_global()
.task_manager()
.dom_manipulation_task_source()
.queue(task!(details_notification_task_steps: move || {
let this = this.root();
if counter == this.toggle_counter.get() {
this.upcast::<EventTarget>().fire_event(atom!("toggle"), CanGc::note());
}
}));
self.upcast::<Node>().dirty(NodeDamage::Other);
}
}
fn children_changed(&self, mutation: &ChildrenMutation) {
self.super_type().unwrap().children_changed(mutation);
self.update_shadow_tree_contents(CanGc::note());
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
self.super_type().unwrap().bind_to_tree(context, can_gc);
self.update_shadow_tree_contents(CanGc::note());
self.update_shadow_tree_styles(CanGc::note());
}
}

View file

@ -0,0 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLDialogElementBinding::HTMLDialogElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{Node, NodeTraits};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDialogElement {
htmlelement: HTMLElement,
return_value: DomRefCell<DOMString>,
}
impl HTMLDialogElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDialogElement {
HTMLDialogElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
return_value: DomRefCell::new(DOMString::new()),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDialogElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDialogElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLDialogElementMethods<crate::DomTypeHolder> for HTMLDialogElement {
// https://html.spec.whatwg.org/multipage/#dom-dialog-open
make_bool_getter!(Open, "open");
// https://html.spec.whatwg.org/multipage/#dom-dialog-open
make_bool_setter!(SetOpen, "open");
// https://html.spec.whatwg.org/multipage/#dom-dialog-returnvalue
fn ReturnValue(&self) -> DOMString {
let return_value = self.return_value.borrow();
return_value.clone()
}
// https://html.spec.whatwg.org/multipage/#dom-dialog-returnvalue
fn SetReturnValue(&self, return_value: DOMString) {
*self.return_value.borrow_mut() = return_value;
}
/// <https://html.spec.whatwg.org/multipage/#dom-dialog-show>
fn Show(&self, can_gc: CanGc) {
let element = self.upcast::<Element>();
// Step 1 TODO: Check is modal flag is false
if element.has_attribute(&local_name!("open")) {
return;
}
// TODO: Step 2 If this has an open attribute, then throw an "InvalidStateError" DOMException.
// Step 3
element.set_bool_attribute(&local_name!("open"), true, can_gc);
// TODO: Step 4 Set this's previously focused element to the focused element.
// TODO: Step 5 Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
// TODO: Step 6 If hideUntil is null, then set hideUntil to this's node document.
// TODO: Step 7 Run hide all popovers until given hideUntil, false, and true.
// TODO(Issue #32702): Step 8 Run the dialog focusing steps given this.
}
// https://html.spec.whatwg.org/multipage/#dom-dialog-close
fn Close(&self, return_value: Option<DOMString>, can_gc: CanGc) {
let element = self.upcast::<Element>();
let target = self.upcast::<EventTarget>();
// Step 1 & 2
if element
.remove_attribute(&ns!(), &local_name!("open"), can_gc)
.is_none()
{
return;
}
// Step 3
if let Some(new_value) = return_value {
*self.return_value.borrow_mut() = new_value;
}
// TODO: Step 4 implement pending dialog stack removal
// Step 5
self.owner_global()
.task_manager()
.dom_manipulation_task_source()
.queue_simple_event(target, atom!("close"));
}
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDirectoryElement {
htmlelement: HTMLElement,
}
impl HTMLDirectoryElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDirectoryElement {
HTMLDirectoryElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDirectoryElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDirectoryElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLDivElementBinding::HTMLDivElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDivElement {
htmlelement: HTMLElement,
}
impl HTMLDivElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDivElement {
HTMLDivElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDivElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDivElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLDivElementMethods<crate::DomTypeHolder> for HTMLDivElement {
// https://html.spec.whatwg.org/multipage/#dom-div-align
make_getter!(Align, "align");
// https://html.spec.whatwg.org/multipage/#dom-div-align
make_setter!(SetAlign, "align");
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLDListElement {
htmlelement: HTMLElement,
}
impl HTMLDListElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLDListElement {
HTMLDListElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLDListElement> {
Node::reflect_node_with_proto(
Box::new(HTMLDListElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
use script_bindings::codegen::GenericBindings::HTMLDocumentBinding::HTMLDocumentMethods;
use script_bindings::root::DomRoot;
use script_bindings::script_runtime::CanGc;
use script_bindings::str::DOMString;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::NamedPropertyValue;
use crate::dom::types::{Document, Location};
/// <https://html.spec.whatwg.org/multipage/#htmldocument>
#[dom_struct]
pub(crate) struct HTMLDocument {
document: Document,
}
impl HTMLDocumentMethods<crate::DomTypeHolder> for HTMLDocument {
/// <https://html.spec.whatwg.org/multipage/#dom-document-location>
fn GetLocation(&self) -> Option<DomRoot<Location>> {
self.document.GetLocation()
}
/// <https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names>
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
self.document.SupportedPropertyNames()
}
/// <https://html.spec.whatwg.org/multipage/#dom-tree-accessors:dom-document-nameditem-filter>
fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<NamedPropertyValue> {
self.document.NamedGetter(name, can_gc)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLEmbedElement {
htmlelement: HTMLElement,
}
impl HTMLEmbedElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLEmbedElement {
HTMLEmbedElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLEmbedElement> {
Node::reflect_node_with_proto(
Box::new(HTMLEmbedElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,288 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use stylo_dom::ElementState;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods;
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlcollection::HTMLCollection;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::html::htmllegendelement::HTMLLegendElement;
use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidityState;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
#[dom_struct]
pub(crate) struct HTMLFieldSetElement {
htmlelement: HTMLElement,
form_owner: MutNullableDom<HTMLFormElement>,
validity_state: MutNullableDom<ValidityState>,
}
impl HTMLFieldSetElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLFieldSetElement {
HTMLFieldSetElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED | ElementState::VALID,
local_name,
prefix,
document,
),
form_owner: Default::default(),
validity_state: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLFieldSetElement> {
Node::reflect_node_with_proto(
Box::new(HTMLFieldSetElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn update_validity(&self, can_gc: CanGc) {
let has_invalid_child = self
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.flat_map(DomRoot::downcast::<Element>)
.any(|element| element.is_invalid(false, can_gc));
self.upcast::<Element>()
.set_state(ElementState::VALID, !has_invalid_child);
self.upcast::<Element>()
.set_state(ElementState::INVALID, has_invalid_child);
}
}
impl HTMLFieldSetElementMethods<crate::DomTypeHolder> for HTMLFieldSetElement {
// https://html.spec.whatwg.org/multipage/#dom-fieldset-elements
fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLCollection> {
HTMLCollection::new_with_filter_fn(
&self.owner_window(),
self.upcast(),
|element, _| {
element
.downcast::<HTMLElement>()
.is_some_and(HTMLElement::is_listed_element)
},
can_gc,
)
}
// https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
make_bool_setter!(SetDisabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
/// <https://html.spec.whatwg.org/multipage/#dom-fieldset-type>
fn Type(&self) -> DOMString {
DOMString::from_string(String::from("fieldset"))
}
}
impl VirtualMethods for HTMLFieldSetElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("disabled") => {
let disabled_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Fieldset was already disabled before.
return;
},
AttributeMutation::Removed => false,
};
let node = self.upcast::<Node>();
let element = self.upcast::<Element>();
element.set_disabled_state(disabled_state);
element.set_enabled_state(!disabled_state);
let mut found_legend = false;
let children = node.children().filter(|node| {
if found_legend {
true
} else if node.is::<HTMLLegendElement>() {
found_legend = true;
false
} else {
true
}
});
let fields = children.flat_map(|child| {
child
.traverse_preorder(ShadowIncluding::No)
.filter(|descendant| match descendant.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLButtonElement |
HTMLElementTypeId::HTMLInputElement |
HTMLElementTypeId::HTMLSelectElement |
HTMLElementTypeId::HTMLTextAreaElement,
)) => true,
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLElement,
)) => descendant
.downcast::<HTMLElement>()
.unwrap()
.is_form_associated_custom_element(),
_ => false,
})
});
if disabled_state {
for field in fields {
let element = field.downcast::<Element>().unwrap();
if element.enabled_state() {
element.set_disabled_state(true);
element.set_enabled_state(false);
if element
.downcast::<HTMLElement>()
.is_some_and(|h| h.is_form_associated_custom_element())
{
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(true),
None,
);
}
}
element.update_sequentially_focusable_status(can_gc);
}
} else {
for field in fields {
let element = field.downcast::<Element>().unwrap();
if element.disabled_state() {
element.check_disabled_attribute();
element.check_ancestors_disabled_state_for_form_control();
// Fire callback only if this has actually enabled the custom element
if element.enabled_state() &&
element
.downcast::<HTMLElement>()
.is_some_and(|h| h.is_form_associated_custom_element())
{
ScriptThread::enqueue_callback_reaction(
element,
CallbackReaction::FormDisabled(false),
None,
);
}
}
element.update_sequentially_focusable_status(can_gc);
}
}
element.update_sequentially_focusable_status(can_gc);
},
local_name!("form") => {
self.form_attribute_mutated(mutation, can_gc);
},
_ => {},
}
}
}
impl FormControl for HTMLFieldSetElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLFieldSetElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
false
}
}

View file

@ -0,0 +1,224 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use cssparser::match_ignore_ascii_case;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::AttrValue;
use style::color::AbsoluteColor;
use style::str::{HTML_SPACE_CHARACTERS, read_numbers};
use style::values::computed::font::{
FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
};
use stylo_atoms::Atom;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLFontElement {
htmlelement: HTMLElement,
}
impl HTMLFontElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLFontElement {
HTMLFontElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLFontElement> {
Node::reflect_node_with_proto(
Box::new(HTMLFontElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
pub(crate) fn parse_face_attribute(face_value: Atom) -> Vec<SingleFontFamily> {
face_value
.split(',')
.map(|string| Self::parse_single_face_value_from_string(string.trim()))
.collect()
}
fn parse_single_face_value_from_string(string: &str) -> SingleFontFamily {
match_ignore_ascii_case! { string,
"serif" => return SingleFontFamily::Generic(GenericFontFamily::Serif),
"sans-serif" => return SingleFontFamily::Generic(GenericFontFamily::SansSerif),
"cursive" => return SingleFontFamily::Generic(GenericFontFamily::Cursive),
"fantasy" => return SingleFontFamily::Generic(GenericFontFamily::Fantasy),
"monospace" => return SingleFontFamily::Generic(GenericFontFamily::Monospace),
"system-ui" => return SingleFontFamily::Generic(GenericFontFamily::SystemUi),
_ => {}
}
let name = string.to_owned().replace(['\'', '"'], "");
let syntax = if name == string {
FontFamilyNameSyntax::Identifiers
} else {
FontFamilyNameSyntax::Quoted
};
SingleFontFamily::FamilyName(FamilyName {
name: name.into(),
syntax,
})
}
}
impl HTMLFontElementMethods<crate::DomTypeHolder> for HTMLFontElement {
// https://html.spec.whatwg.org/multipage/#dom-font-color
make_getter!(Color, "color");
// https://html.spec.whatwg.org/multipage/#dom-font-color
make_legacy_color_setter!(SetColor, "color");
// https://html.spec.whatwg.org/multipage/#dom-font-face
make_getter!(Face, "face");
// https://html.spec.whatwg.org/multipage/#dom-font-face
make_atomic_setter!(SetFace, "face");
// https://html.spec.whatwg.org/multipage/#dom-font-size
make_getter!(Size, "size");
// https://html.spec.whatwg.org/multipage/#dom-font-size
fn SetSize(&self, value: DOMString, can_gc: CanGc) {
let element = self.upcast::<Element>();
element.set_attribute(&local_name!("size"), parse_size(&value), can_gc);
}
}
impl VirtualMethods for HTMLFontElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
if attr.local_name() == &local_name!("color") ||
attr.local_name() == &local_name!("size") ||
attr.local_name() == &local_name!("face")
{
return true;
}
self.super_type()
.unwrap()
.attribute_affects_presentational_hints(attr)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("face") => AttrValue::from_atomic(value.into()),
local_name!("color") => AttrValue::from_legacy_color(value.into()),
local_name!("size") => parse_size(&value),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}
pub(crate) trait HTMLFontElementLayoutHelpers {
fn get_color(self) -> Option<AbsoluteColor>;
fn get_face(self) -> Option<Atom>;
fn get_size(self) -> Option<u32>;
}
impl HTMLFontElementLayoutHelpers for LayoutDom<'_, HTMLFontElement> {
fn get_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("color"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_face(self) -> Option<Atom> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("face"))
.map(AttrValue::as_atom)
.cloned()
}
fn get_size(self) -> Option<u32> {
let size = self
.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("size"));
match size {
Some(&AttrValue::UInt(_, s)) => Some(s),
_ => None,
}
}
}
/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size>
fn parse_size(mut input: &str) -> AttrValue {
let original_input = input;
// Steps 1 & 2 are not relevant
// Step 3
input = input.trim_matches(HTML_SPACE_CHARACTERS);
enum ParseMode {
RelativePlus,
RelativeMinus,
Absolute,
}
let mut input_chars = input.chars().peekable();
let parse_mode = match input_chars.peek() {
// Step 4
None => return AttrValue::String(original_input.into()),
// Step 5
Some(&'+') => {
let _ = input_chars.next(); // consume the '+'
ParseMode::RelativePlus
},
Some(&'-') => {
let _ = input_chars.next(); // consume the '-'
ParseMode::RelativeMinus
},
Some(_) => ParseMode::Absolute,
};
// Steps 6, 7, 8
let mut value = match read_numbers(input_chars) {
(Some(v), _) if v >= 0 => v,
_ => return AttrValue::String(original_input.into()),
};
// Step 9
match parse_mode {
ParseMode::RelativePlus => value += 3,
ParseMode::RelativeMinus => value = 3 - value,
ParseMode::Absolute => (),
}
// Steps 10, 11, 12
AttrValue::UInt(original_input.into(), value as u32)
}

View file

@ -0,0 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use stylo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::element::Element;
use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
use crate::dom::html::htmlformelement::HTMLFormElement;
use crate::dom::node::Node;
use crate::dom::radionodelist::RadioNodeList;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLFormControlsCollection {
collection: HTMLCollection,
form: Dom<HTMLFormElement>,
}
impl HTMLFormControlsCollection {
fn new_inherited(
form: &HTMLFormElement,
filter: Box<dyn CollectionFilter + 'static>,
) -> HTMLFormControlsCollection {
let root_of_form = form
.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty());
HTMLFormControlsCollection {
collection: HTMLCollection::new_inherited(&root_of_form, filter),
form: Dom::from_ref(form),
}
}
pub(crate) fn new(
window: &Window,
form: &HTMLFormElement,
filter: Box<dyn CollectionFilter + 'static>,
can_gc: CanGc,
) -> DomRoot<HTMLFormControlsCollection> {
reflect_dom_object(
Box::new(HTMLFormControlsCollection::new_inherited(form, filter)),
window,
can_gc,
)
}
}
impl HTMLFormControlsCollectionMethods<crate::DomTypeHolder> for HTMLFormControlsCollection {
// FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
// HTMLFormControlsCollection) implements Length
// https://dom.spec.whatwg.org/#dom-htmlcollection-length
fn Length(&self) -> u32 {
self.collection.Length()
}
// https://html.spec.whatwg.org/multipage/#dom-htmlformcontrolscollection-nameditem
fn NamedItem(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
// Step 1
if name.is_empty() {
return None;
}
let name = Atom::from(name);
let mut filter_map = self.collection.elements_iter().filter_map(|elem| {
if elem.get_name().is_some_and(|n| n == name) ||
elem.get_id().is_some_and(|i| i == name)
{
Some(elem)
} else {
None
}
});
if let Some(elem) = filter_map.next() {
let mut peekable = filter_map.peekable();
// Step 2
if peekable.peek().is_none() {
Some(RadioNodeListOrElement::Element(elem))
} else {
// Step 4-5
let global = self.global();
let window = global.as_window();
// There is only one way to get an HTMLCollection,
// specifically HTMLFormElement::Elements(),
// and the collection filter excludes image inputs.
Some(RadioNodeListOrElement::RadioNodeList(
RadioNodeList::new_controls_except_image_inputs(
window, &self.form, &name, can_gc,
),
))
}
// Step 3
} else {
None
}
}
// https://html.spec.whatwg.org/multipage/#dom-htmlformcontrolscollection-nameditem
fn NamedGetter(&self, name: DOMString, can_gc: CanGc) -> Option<RadioNodeListOrElement> {
self.NamedItem(name, can_gc)
}
// https://html.spec.whatwg.org/multipage/#the-htmlformcontrolscollection-interface:supported-property-names
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
self.collection.SupportedPropertyNames()
}
// FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
// HTMLFormControlsCollection) implements IndexedGetter.
// https://github.com/servo/servo/issues/5875
//
// https://dom.spec.whatwg.org/#dom-htmlcollection-item
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
self.collection.IndexedGetter(index)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLFrameElement {
htmlelement: HTMLElement,
}
impl HTMLFrameElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLFrameElement {
HTMLFrameElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLFrameElement> {
Node::reflect_node_with_proto(
Box::new(HTMLFrameElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding::HTMLFrameSetElementMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{Node, NodeTraits};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLFrameSetElement {
htmlelement: HTMLElement,
}
impl HTMLFrameSetElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLFrameSetElement {
HTMLFrameSetElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLFrameSetElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLFrameSetElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}
impl HTMLFrameSetElementMethods<crate::DomTypeHolder> for HTMLFrameSetElement {
// https://html.spec.whatwg.org/multipage/#windoweventhandlers
window_event_handlers!(ForwardToWindow);
}

View file

@ -0,0 +1,110 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use content_security_policy::{CspList, PolicyDisposition, PolicySource};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlmetaelement::HTMLMetaElement;
use crate::dom::node::{BindContext, Node, NodeTraits, ShadowIncluding};
use crate::dom::userscripts::load_script;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLHeadElement {
htmlelement: HTMLElement,
}
impl HTMLHeadElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLHeadElement {
HTMLHeadElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLHeadElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLHeadElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
pub(crate) fn set_content_security_policy(&self) {
let doc = self.owner_document();
if doc.GetHead().as_deref() != Some(self) {
return;
}
let mut csp_list: Option<CspList> = None;
let node = self.upcast::<Node>();
let candidates = node
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<Element>)
.filter(|elem| elem.is::<HTMLMetaElement>())
.filter(|elem| {
elem.get_string_attribute(&local_name!("http-equiv"))
.to_ascii_lowercase() ==
*"content-security-policy"
})
.filter(|elem| {
elem.get_attribute(&ns!(), &local_name!("content"))
.is_some()
});
for meta in candidates {
if let Some(ref content) = meta.get_attribute(&ns!(), &local_name!("content")) {
let content = content.value();
let content_val = content.trim();
if !content_val.is_empty() {
let policies =
CspList::parse(content_val, PolicySource::Meta, PolicyDisposition::Enforce);
match csp_list {
Some(ref mut csp_list) => csp_list.append(policies),
None => csp_list = Some(policies),
}
}
}
}
doc.set_csp_list(csp_list);
}
}
impl VirtualMethods for HTMLHeadElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
load_script(self);
}
}

View file

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) enum HeadingLevel {
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
}
#[dom_struct]
pub(crate) struct HTMLHeadingElement {
htmlelement: HTMLElement,
level: HeadingLevel,
}
impl HTMLHeadingElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
level: HeadingLevel,
) -> HTMLHeadingElement {
HTMLHeadingElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
level,
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
level: HeadingLevel,
can_gc: CanGc,
) -> DomRoot<HTMLHeadingElement> {
Node::reflect_node_with_proto(
Box::new(HTMLHeadingElement::new_inherited(
local_name, prefix, document, level,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,171 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::str::FromStr;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use style::values::generics::NonNegative;
use style::values::specified::border::BorderSideWidth;
use style::values::specified::length::Size;
use style::values::specified::{LengthPercentage, NoCalcLength};
use crate::dom::bindings::codegen::Bindings::HTMLHRElementBinding::HTMLHRElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLHRElement {
htmlelement: HTMLElement,
}
impl HTMLHRElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLHRElement {
HTMLHRElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLHRElement> {
Node::reflect_node_with_proto(
Box::new(HTMLHRElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLHRElementMethods<crate::DomTypeHolder> for HTMLHRElement {
// https://html.spec.whatwg.org/multipage/#dom-hr-align
make_getter!(Align, "align");
// https://html.spec.whatwg.org/multipage/#dom-hr-align
make_atomic_setter!(SetAlign, "align");
// https://html.spec.whatwg.org/multipage/#dom-hr-color
make_getter!(Color, "color");
// https://html.spec.whatwg.org/multipage/#dom-hr-color
make_legacy_color_setter!(SetColor, "color");
// https://html.spec.whatwg.org/multipage/#dom-hr-noshade
make_bool_getter!(NoShade, "noshade");
// https://html.spec.whatwg.org/multipage/#dom-hr-noshade
make_bool_setter!(SetNoShade, "noshade");
// https://html.spec.whatwg.org/multipage/#dom-hr-size
make_getter!(Size, "size");
// https://html.spec.whatwg.org/multipage/#dom-hr-size
make_dimension_setter!(SetSize, "size");
// https://html.spec.whatwg.org/multipage/#dom-hr-width
make_getter!(Width, "width");
// https://html.spec.whatwg.org/multipage/#dom-hr-width
make_dimension_setter!(SetWidth, "width");
}
/// The result of applying the the presentational hint for the `size` attribute.
///
/// (This attribute can mean different things depending on its value and other attributes)
#[allow(clippy::enum_variant_names)]
pub(crate) enum SizePresentationalHint {
SetHeightTo(Size),
SetAllBorderWidthValuesTo(BorderSideWidth),
SetBottomBorderWidthToZero,
}
pub(crate) trait HTMLHRLayoutHelpers {
fn get_color(self) -> Option<AbsoluteColor>;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_size_info(self) -> Option<SizePresentationalHint>;
}
impl HTMLHRLayoutHelpers for LayoutDom<'_, HTMLHRElement> {
fn get_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("color"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_size_info(self) -> Option<SizePresentationalHint> {
// https://html.spec.whatwg.org/multipage/#the-hr-element-2
let element = self.upcast::<Element>();
let size_value = element
.get_attr_val_for_layout(&ns!(), &local_name!("size"))
.and_then(|value| usize::from_str(value).ok())
.filter(|value| *value != 0)?;
let hint = if element
.get_attr_for_layout(&ns!(), &local_name!("color"))
.is_some() ||
element
.get_attr_for_layout(&ns!(), &local_name!("noshade"))
.is_some()
{
SizePresentationalHint::SetAllBorderWidthValuesTo(BorderSideWidth::from_px(
size_value as f32 / 2.0,
))
} else if size_value == 1 {
SizePresentationalHint::SetBottomBorderWidthToZero
} else {
SizePresentationalHint::SetHeightTo(Size::LengthPercentage(NonNegative(
LengthPercentage::Length(NoCalcLength::from_px((size_value - 2) as f32)),
)))
};
Some(hint)
}
}
impl VirtualMethods for HTMLHRElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("align") => AttrValue::from_dimension(value.into()),
local_name!("color") => AttrValue::from_legacy_color(value.into()),
local_name!("width") => AttrValue::from_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLHtmlElement {
htmlelement: HTMLElement,
}
#[allow(non_snake_case)]
impl HTMLHtmlElement {
fn new_inherited(
localName: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLHtmlElement {
HTMLHtmlElement {
htmlelement: HTMLElement::new_inherited(localName, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
localName: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLHtmlElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLHtmlElement::new_inherited(localName, prefix, document)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}

View file

@ -0,0 +1,493 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use html5ever::{local_name, ns};
use servo_url::ServoUrl;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::conversions::DerivedFrom;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::element::Element;
use crate::dom::node::NodeTraits;
use crate::dom::urlhelper::UrlHelper;
use crate::script_runtime::CanGc;
pub(crate) trait HyperlinkElement {
fn get_url(&self) -> &DomRefCell<Option<ServoUrl>>;
}
/// <https://html.spec.whatwg.org/multipage/#htmlhyperlinkelementutils>
pub(crate) trait HyperlinkElementTraits {
fn get_hash(&self) -> USVString;
fn set_hash(&self, value: USVString, can_gc: CanGc);
fn get_host(&self) -> USVString;
fn set_host(&self, value: USVString, can_gc: CanGc);
fn get_hostname(&self) -> USVString;
fn set_hostname(&self, value: USVString, can_gc: CanGc);
fn get_href(&self) -> USVString;
fn set_href(&self, value: USVString, can_gc: CanGc);
fn get_origin(&self) -> USVString;
fn get_password(&self) -> USVString;
fn set_password(&self, value: USVString, can_gc: CanGc);
fn get_pathname(&self) -> USVString;
fn set_pathname(&self, value: USVString, can_gc: CanGc);
fn get_port(&self) -> USVString;
fn set_port(&self, value: USVString, can_gc: CanGc);
fn get_protocol(&self) -> USVString;
fn set_protocol(&self, value: USVString, can_gc: CanGc);
fn get_search(&self) -> USVString;
fn set_search(&self, value: USVString, can_gc: CanGc);
fn get_username(&self) -> USVString;
fn set_url(&self);
fn set_username(&self, value: USVString, can_gc: CanGc);
fn update_href(&self, url: DOMString, can_gc: CanGc);
fn reinitialize_url(&self);
}
impl<T: HyperlinkElement + DerivedFrom<Element> + Castable + NodeTraits> HyperlinkElementTraits
for T
{
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn get_hash(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url is null, or url's fragment is either null or the empty string, return
// the empty string.
None => USVString(String::new()),
Some(ref url) if url.fragment().is_none() || url.fragment() == Some("") => {
USVString(String::new())
},
Some(ref url) => {
// Step 4. Return "#", followed by url's fragment.
UrlHelper::Hash(url)
},
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hash>
fn set_hash(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null, then return.
None => return,
// Step 4. If the given value is the empty string, set url's fragment to null.
// Note this step is taken care of by UrlHelper::SetHash when the value is Some
// Steps 5. Otherwise:
Some(url) => {
// Step 5.1. Let input be the given value with a single leading "#" removed, if any.
// Step 5.2. Set url's fragment to the empty string.
// Note these steps are taken care of by UrlHelper::SetHash
UrlHelper::SetHash(url, value);
// Step 5.4. Basic URL parse input, with url as url and fragment state as state
// override.
DOMString::from(url.as_str())
},
};
// Step 6. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn get_host(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url or url's host is null, return the empty string.
None => USVString(String::new()),
Some(ref url) => {
if url.host().is_none() {
USVString(String::new())
} else {
// Step 4. If url's port is null, return url's host, serialized.
// Step 5. Return url's host, serialized, followed by ":" and url's port,
// serialized.
UrlHelper::Host(url)
}
},
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-host>
fn set_host(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url or url's host is null, return the empty string.
Some(ref url) if url.cannot_be_a_base() => return,
None => return,
// Step 4. Basic URL parse the given value, with url as url and host state as state
// override.
Some(url) => {
UrlHelper::SetHost(url, value);
DOMString::from(url.as_str())
},
};
// Step 5. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn get_hostname(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url or url's host is null, return the empty string.
None => USVString(String::new()),
Some(ref url) => {
// Step 4. Return url's host, serialized.
UrlHelper::Hostname(url)
},
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-hostname>
fn set_hostname(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null or url has an opaque path, then return.
None => return,
Some(ref url) if url.cannot_be_a_base() => return,
// Step 4. Basic URL parse the given value, with url as url and hostname state as state
// override.
Some(url) => {
UrlHelper::SetHostname(url, value);
DOMString::from(url.as_str())
},
};
// Step 5. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href>
fn get_href(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
USVString(match *self.get_url().borrow() {
None => {
match self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("href"))
{
// Step 3. If url is null and this has no href content attribute, return the
// empty string.
None => String::new(),
// Step 4. Otherwise, if url is null, return this's href content attribute's value.
Some(attribute) => (**attribute.value()).to_owned(),
}
},
// Step 5. Return url, serialized.
Some(ref url) => url.as_str().to_owned(),
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-href
fn set_href(&self, value: USVString, can_gc: CanGc) {
self.upcast::<Element>().set_string_attribute(
&local_name!("href"),
DOMString::from_string(value.0),
can_gc,
);
self.set_url();
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-origin>
fn get_origin(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
USVString(match *self.get_url().borrow() {
// Step 2. If this's url is null, return the empty string.
None => "".to_owned(),
// Step 3. Return the serialization of this's url's origin.
Some(ref url) => url.origin().ascii_serialization(),
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn get_password(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url is null, then return the empty string.
None => USVString(String::new()),
// Steps 4. Return url's password.
Some(ref url) => UrlHelper::Password(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-password>
fn set_password(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null or url cannot have a username/password/port, then return.
None => return,
Some(ref url) if url.host().is_none() || url.cannot_be_a_base() => return,
// Step 4. Set the password, given url and the given value.
Some(url) => {
UrlHelper::SetPassword(url, value);
DOMString::from(url.as_str())
},
};
// Step 5. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn get_pathname(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url is null, then return the empty string.
None => USVString(String::new()),
// Steps 4. Return the result of URL path serializing url.
Some(ref url) => UrlHelper::Pathname(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname>
fn set_pathname(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null or url has an opaque path, then return.
None => return,
Some(ref url) if url.cannot_be_a_base() => return,
// Step 4. Set url's path to the empty list.
// Step 5. Basic URL parse the given value, with url as url and path start state as state override.
Some(url) => {
UrlHelper::SetPathname(url, value);
DOMString::from(url.as_str())
},
};
// Step 6. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn get_port(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url or url's port is null, return the empty string.
None => USVString(String::new()),
// Step 4. Return url's port, serialized.
Some(ref url) => UrlHelper::Port(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-port>
fn set_port(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null or url cannot have a username/password/port, then return.
None => return,
Some(ref url)
// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
if url.host().is_none() || url.cannot_be_a_base() || url.scheme() == "file" =>
{
return;
},
// Step 4. If the given value is the empty string, then set url's port to null.
// Step 5. Otherwise, basic URL parse the given value, with url as url and port state as
// state override.
Some(url) => {
UrlHelper::SetPort(url, value);
DOMString::from(url.as_str())
},
};
// Step 6. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn get_protocol(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
match *self.get_url().borrow() {
// Step 2. If this's url is null, return ":".
None => USVString(":".to_owned()),
// Step 3. Return this's url's scheme, followed by ":".
Some(ref url) => UrlHelper::Protocol(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-protocol>
fn set_protocol(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
let url = match self.get_url().borrow_mut().as_mut() {
// Step 2. If this's url is null, then return.
None => return,
// Step 3. Basic URL parse the given value, followed by ":", with this's url as url and
// scheme start state as state override.
Some(url) => {
UrlHelper::SetProtocol(url, value);
DOMString::from(url.as_str())
},
};
// Step 4. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn get_search(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
match *self.get_url().borrow() {
// Step 3. If url is null, or url's query is either null or the empty string, return the
// empty string.
// Step 4. Return "?", followed by url's query.
// Note: This is handled in UrlHelper::Search
None => USVString(String::new()),
Some(ref url) => UrlHelper::Search(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-search>
fn set_search(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null, terminate these steps.
None => return,
// Step 4. If the given value is the empty string, set url's query to null.
// Step 5. Otherwise:
Some(url) => {
// Note: Inner steps are handled by UrlHelper::SetSearch
UrlHelper::SetSearch(url, value);
DOMString::from(url.as_str())
},
};
// Step 6. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn get_username(&self) -> USVString {
// Step 1. Reinitialize url.
self.reinitialize_url();
match *self.get_url().borrow() {
// Step 2. If this's url is null, return the empty string.
None => USVString(String::new()),
// Step 3. Return this's url's username.
Some(ref url) => UrlHelper::Username(url),
}
}
/// <https://html.spec.whatwg.org/multipage/#concept-hyperlink-url-set>
fn set_url(&self) {
// Step 1. Set this element's url to null.
*self.get_url().borrow_mut() = None;
let attribute = self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("href"));
// Step 2. If this element's href content attribute is absent, then return.
let Some(attribute) = attribute else {
return;
};
let document = self.owner_document();
// Step 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.
let url = document.encoding_parse_a_url(&attribute.value());
// Step 4. If url is not failure, then set this element's url to url.
if let Ok(url) = url {
*self.get_url().borrow_mut() = Some(url);
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-hyperlink-username>
fn set_username(&self, value: USVString, can_gc: CanGc) {
// Step 1. Reinitialize url.
self.reinitialize_url();
// Step 2. Let url be this's url.
let url = match self.get_url().borrow_mut().as_mut() {
// Step 3. If url is null or url cannot have a username/password/port, then return.
None => return,
Some(ref url) if url.host().is_none() || url.cannot_be_a_base() => return,
// Step 4. Set the username, given url and the given value.
Some(url) => {
UrlHelper::SetUsername(url, value);
DOMString::from(url.as_str())
},
};
// Step 5. Update href.
self.update_href(url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#update-href>
fn update_href(&self, url: DOMString, can_gc: CanGc) {
self.upcast::<Element>()
.set_string_attribute(&local_name!("href"), url, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#reinitialise-url>
fn reinitialize_url(&self) {
// Step 1. If the element's url is non-null, its scheme is "blob", and it has an opaque
// path, then terminate these steps.
match *self.get_url().borrow() {
Some(ref url) if url.scheme() == "blob" && url.cannot_be_a_base() => return,
_ => (),
}
// Step 2. Set the url.
self.set_url();
}
}

View file

@ -0,0 +1,871 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use bitflags::bitflags;
use constellation_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
use constellation_traits::{
IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, LoadOrigin,
NavigationHistoryBehavior, ScriptToConstellationMessage,
};
use dom_struct::dom_struct;
use embedder_traits::ViewportDetails;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use net_traits::ReferrerPolicy;
use net_traits::request::Destination;
use profile_traits::ipc as ProfiledIpc;
use script_traits::{NewLayoutInfo, UpdatePipelineIdReason};
use servo_url::ServoUrl;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use stylo_atoms::Atom;
use crate::document_loader::{LoadBlocker, LoadType};
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::{Document, determine_policy_for_token};
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{
AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute,
};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, UnbindContext};
use crate::dom::trustedhtml::TrustedHTML;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::windowproxy::WindowProxy;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
struct SandboxAllowance(u8);
bitflags! {
impl SandboxAllowance: u8 {
const ALLOW_NOTHING = 0x00;
const ALLOW_SAME_ORIGIN = 0x01;
const ALLOW_TOP_NAVIGATION = 0x02;
const ALLOW_FORMS = 0x04;
const ALLOW_SCRIPTS = 0x08;
const ALLOW_POINTER_LOCK = 0x10;
const ALLOW_POPUPS = 0x20;
}
}
#[derive(PartialEq)]
enum PipelineType {
InitialAboutBlank,
Navigation,
}
#[derive(PartialEq)]
enum ProcessingMode {
FirstTime,
NotFirstTime,
}
#[dom_struct]
pub(crate) struct HTMLIFrameElement {
htmlelement: HTMLElement,
#[no_trace]
webview_id: Cell<Option<WebViewId>>,
#[no_trace]
browsing_context_id: Cell<Option<BrowsingContextId>>,
#[no_trace]
pipeline_id: Cell<Option<PipelineId>>,
#[no_trace]
pending_pipeline_id: Cell<Option<PipelineId>>,
#[no_trace]
about_blank_pipeline_id: Cell<Option<PipelineId>>,
sandbox: MutNullableDom<DOMTokenList>,
sandbox_allowance: Cell<Option<SandboxAllowance>>,
load_blocker: DomRefCell<Option<LoadBlocker>>,
throttled: Cell<bool>,
}
impl HTMLIFrameElement {
pub(crate) fn is_sandboxed(&self) -> bool {
self.sandbox_allowance.get().is_some()
}
/// <https://html.spec.whatwg.org/multipage/#otherwise-steps-for-iframe-or-frame-elements>,
/// step 1.
fn get_url(&self) -> ServoUrl {
let element = self.upcast::<Element>();
element
.get_attribute(&ns!(), &local_name!("src"))
.and_then(|src| {
let url = src.value();
if url.is_empty() {
None
} else {
self.owner_document().base_url().join(&url).ok()
}
})
.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap())
}
pub(crate) fn navigate_or_reload_child_browsing_context(
&self,
load_data: LoadData,
history_handling: NavigationHistoryBehavior,
can_gc: CanGc,
) {
self.start_new_pipeline(
load_data,
PipelineType::Navigation,
history_handling,
can_gc,
);
}
fn start_new_pipeline(
&self,
mut load_data: LoadData,
pipeline_type: PipelineType,
history_handling: NavigationHistoryBehavior,
can_gc: CanGc,
) {
let sandboxed = if self.is_sandboxed() {
IFrameSandboxed
} else {
IFrameUnsandboxed
};
let browsing_context_id = match self.browsing_context_id() {
None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
Some(id) => id,
};
let webview_id = match self.webview_id() {
None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
Some(id) => id,
};
let document = self.owner_document();
{
let load_blocker = &self.load_blocker;
// Any oustanding load is finished from the point of view of the blocked
// document; the new navigation will continue blocking it.
LoadBlocker::terminate(load_blocker, can_gc);
}
if load_data.url.scheme() == "javascript" {
let window_proxy = self.GetContentWindow();
if let Some(window_proxy) = window_proxy {
if !ScriptThread::navigate_to_javascript_url(
&document.global(),
&window_proxy.global(),
&mut load_data,
Some(self.upcast()),
can_gc,
) {
return;
}
}
}
match load_data.js_eval_result {
Some(JsEvalResult::NoContent) => (),
_ => {
let mut load_blocker = self.load_blocker.borrow_mut();
*load_blocker = Some(LoadBlocker::new(
&document,
LoadType::Subframe(load_data.url.clone()),
));
},
};
let window = self.owner_window();
let old_pipeline_id = self.pipeline_id();
let new_pipeline_id = PipelineId::new();
self.pending_pipeline_id.set(Some(new_pipeline_id));
let load_info = IFrameLoadInfo {
parent_pipeline_id: window.pipeline_id(),
browsing_context_id,
webview_id,
new_pipeline_id,
is_private: false, // FIXME
inherited_secure_context: load_data.inherited_secure_context,
history_handling,
};
let viewport_details = window
.get_iframe_viewport_details_if_known(browsing_context_id)
.unwrap_or_else(|| ViewportDetails {
hidpi_scale_factor: window.device_pixel_ratio(),
..Default::default()
});
match pipeline_type {
PipelineType::InitialAboutBlank => {
self.about_blank_pipeline_id.set(Some(new_pipeline_id));
let load_info = IFrameLoadInfoWithData {
info: load_info,
load_data: load_data.clone(),
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()
.script_to_constellation_chan()
.send(ScriptToConstellationMessage::ScriptNewIFrame(load_info))
.unwrap();
let new_layout_info = NewLayoutInfo {
parent_info: Some(window.pipeline_id()),
new_pipeline_id,
browsing_context_id,
webview_id,
opener: None,
load_data,
viewport_details,
theme: window.theme(),
};
self.pipeline_id.set(Some(new_pipeline_id));
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
},
PipelineType::Navigation => {
let load_info = IFrameLoadInfoWithData {
info: load_info,
load_data,
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()
.script_to_constellation_chan()
.send(ScriptToConstellationMessage::ScriptLoadedURLInIFrame(
load_info,
))
.unwrap();
},
}
}
/// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
fn process_the_iframe_attributes(&self, mode: ProcessingMode, can_gc: CanGc) {
// > 1. If `element`'s `srcdoc` attribute is specified, then:
if self
.upcast::<Element>()
.has_attribute(&local_name!("srcdoc"))
{
let url = ServoUrl::parse("about:srcdoc").unwrap();
let document = self.owner_document();
let window = self.owner_window();
let pipeline_id = Some(window.pipeline_id());
let mut load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url,
pipeline_id,
window.as_global_scope().get_referrer(),
document.get_referrer_policy(),
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let element = self.upcast::<Element>();
load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc")));
self.navigate_or_reload_child_browsing_context(
load_data,
NavigationHistoryBehavior::Push,
can_gc,
);
return;
}
let window = self.owner_window();
// https://html.spec.whatwg.org/multipage/#attr-iframe-name
// Note: the spec says to set the name 'when the nested browsing context is created'.
// The current implementation sets the name on the window,
// when the iframe attributes are first processed.
if mode == ProcessingMode::FirstTime {
if let Some(window) = self.GetContentWindow() {
window.set_name(
self.upcast::<Element>()
.get_name()
.map_or(DOMString::from(""), |n| DOMString::from(&*n)),
);
}
}
if mode == ProcessingMode::FirstTime &&
!self.upcast::<Element>().has_attribute(&local_name!("src"))
{
return;
}
// > 2. Otherwise, if `element` has a `src` attribute specified, or
// > `initialInsertion` is false, then run the shared attribute
// > processing steps for `iframe` and `frame` elements given
// > `element`.
let url = self.get_url();
// Step 2.4: Let referrerPolicy be the current state of element's referrerpolicy content
// attribute.
let document = self.owner_document();
let referrer_policy_token = self.ReferrerPolicy();
// Note: despite not being explicitly stated in the spec steps, this falls back to
// document's referrer policy here because it satisfies the expectations that when unset,
// the iframe should inherit the referrer policy of its parent
let referrer_policy = match determine_policy_for_token(referrer_policy_token.str()) {
ReferrerPolicy::EmptyString => document.get_referrer_policy(),
policy => policy,
};
// TODO(#25748):
// By spec, we return early if there's an ancestor browsing context
// "whose active document's url, ignoring fragments, is equal".
// However, asking about ancestor browsing contexts is more nuanced than
// it sounds and not implemented here.
// Within a single origin, we can do it by walking window proxies,
// and this check covers only that single-origin case, protecting
// against simple typo self-includes but nothing more elaborate.
let mut ancestor = window.GetParent();
while let Some(a) = ancestor {
if let Some(ancestor_url) = a.document().map(|d| d.url()) {
if ancestor_url.scheme() == url.scheme() &&
ancestor_url.username() == url.username() &&
ancestor_url.password() == url.password() &&
ancestor_url.host() == url.host() &&
ancestor_url.port() == url.port() &&
ancestor_url.path() == url.path() &&
ancestor_url.query() == url.query()
{
return;
}
}
ancestor = a.parent().map(DomRoot::from_ref);
}
let creator_pipeline_id = if url.as_str() == "about:blank" {
Some(window.pipeline_id())
} else {
None
};
let mut load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url,
creator_pipeline_id,
window.as_global_scope().get_referrer(),
referrer_policy,
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let pipeline_id = self.pipeline_id();
// If the initial `about:blank` page is the current page, load with replacement enabled,
// see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3
let is_about_blank =
pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
let history_handling = if is_about_blank {
NavigationHistoryBehavior::Replace
} else {
NavigationHistoryBehavior::Push
};
self.navigate_or_reload_child_browsing_context(load_data, history_handling, can_gc);
}
fn create_nested_browsing_context(&self, can_gc: CanGc) {
// Synchronously create a new browsing context, which will present
// `about:blank`. (This is not a navigation.)
//
// The pipeline started here will synchronously "completely finish
// loading", which will then asynchronously call
// `iframe_load_event_steps`.
//
// The precise event timing differs between implementations and
// remains controversial:
//
// - [Unclear "iframe load event steps" for initial load of about:blank
// in an iframe #490](https://github.com/whatwg/html/issues/490)
// - [load event handling for iframes with no src may not be web
// compatible #4965](https://github.com/whatwg/html/issues/4965)
//
let url = ServoUrl::parse("about:blank").unwrap();
let document = self.owner_document();
let window = self.owner_window();
let pipeline_id = Some(window.pipeline_id());
let mut load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url,
pipeline_id,
window.as_global_scope().get_referrer(),
document.get_referrer_policy(),
Some(window.as_global_scope().is_secure_context()),
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let browsing_context_id = BrowsingContextId::new();
let webview_id = window.window_proxy().webview_id();
self.pipeline_id.set(None);
self.pending_pipeline_id.set(None);
self.webview_id.set(Some(webview_id));
self.browsing_context_id.set(Some(browsing_context_id));
self.start_new_pipeline(
load_data,
PipelineType::InitialAboutBlank,
NavigationHistoryBehavior::Push,
can_gc,
);
}
fn destroy_nested_browsing_context(&self) {
self.pipeline_id.set(None);
self.pending_pipeline_id.set(None);
self.about_blank_pipeline_id.set(None);
self.webview_id.set(None);
self.browsing_context_id.set(None);
}
pub(crate) fn update_pipeline_id(
&self,
new_pipeline_id: PipelineId,
reason: UpdatePipelineIdReason,
can_gc: CanGc,
) {
if self.pending_pipeline_id.get() != Some(new_pipeline_id) &&
reason == UpdatePipelineIdReason::Navigation
{
return;
}
self.pipeline_id.set(Some(new_pipeline_id));
// Only terminate the load blocker if the pipeline id was updated due to a traversal.
// The load blocker will be terminated for a navigation in iframe_load_event_steps.
if reason == UpdatePipelineIdReason::Traversal {
let blocker = &self.load_blocker;
LoadBlocker::terminate(blocker, can_gc);
}
self.upcast::<Node>().dirty(NodeDamage::Other);
}
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLIFrameElement {
HTMLIFrameElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
browsing_context_id: Cell::new(None),
webview_id: Cell::new(None),
pipeline_id: Cell::new(None),
pending_pipeline_id: Cell::new(None),
about_blank_pipeline_id: Cell::new(None),
sandbox: Default::default(),
sandbox_allowance: Cell::new(None),
load_blocker: DomRefCell::new(None),
throttled: Cell::new(false),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLIFrameElement> {
Node::reflect_node_with_proto(
Box::new(HTMLIFrameElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
#[inline]
pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
self.pipeline_id.get()
}
#[inline]
pub(crate) fn browsing_context_id(&self) -> Option<BrowsingContextId> {
self.browsing_context_id.get()
}
#[inline]
pub(crate) fn webview_id(&self) -> Option<WebViewId> {
self.webview_id.get()
}
pub(crate) fn set_throttled(&self, throttled: bool) {
if self.throttled.get() != throttled {
self.throttled.set(throttled);
}
}
/// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps> steps 1-4
pub(crate) fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId, can_gc: CanGc) {
// TODO(#9592): assert that the load blocker is present at all times when we
// can guarantee that it's created for the case of iframe.reload().
if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
return;
}
// TODO A cross-origin child document would not be easily accessible
// from this script thread. It's unclear how to implement
// steps 2, 3, and 5 efficiently in this case.
// TODO Step 2 - check child document `mute iframe load` flag
// TODO Step 3 - set child document `mut iframe load` flag
// Step 4
self.upcast::<EventTarget>()
.fire_event(atom!("load"), can_gc);
let blocker = &self.load_blocker;
LoadBlocker::terminate(blocker, can_gc);
// TODO Step 5 - unset child document `mut iframe load` flag
}
}
pub(crate) trait HTMLIFrameElementLayoutMethods {
fn pipeline_id(self) -> Option<PipelineId>;
fn browsing_context_id(self) -> Option<BrowsingContextId>;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl HTMLIFrameElementLayoutMethods for LayoutDom<'_, HTMLIFrameElement> {
#[inline]
fn pipeline_id(self) -> Option<PipelineId> {
(self.unsafe_get()).pipeline_id.get()
}
#[inline]
fn browsing_context_id(self) -> Option<BrowsingContextId> {
(self.unsafe_get()).browsing_context_id.get()
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl HTMLIFrameElementMethods<crate::DomTypeHolder> for HTMLIFrameElement {
// https://html.spec.whatwg.org/multipage/#dom-iframe-src
make_url_getter!(Src, "src");
// https://html.spec.whatwg.org/multipage/#dom-iframe-src
make_url_setter!(SetSrc, "src");
// https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc
fn Srcdoc(&self) -> TrustedHTMLOrString {
let element = self.upcast::<Element>();
element.get_trusted_html_attribute(&local_name!("srcdoc"))
}
// https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc
fn SetSrcdoc(&self, value: TrustedHTMLOrString, can_gc: CanGc) -> Fallible<()> {
// Step 1: Let compliantString be the result of invoking the
// Get Trusted Type compliant string algorithm with TrustedHTML,
// this's relevant global object, the given value, "HTMLIFrameElement srcdoc", and "script".
let element = self.upcast::<Element>();
let value = TrustedHTML::get_trusted_script_compliant_string(
&element.owner_global(),
value,
"HTMLIFrameElement srcdoc",
can_gc,
)?;
// Step 2: Set an attribute value given this, srcdoc's local name, and compliantString.
element.set_attribute(
&local_name!("srcdoc"),
AttrValue::String(value.as_ref().to_owned()),
can_gc,
);
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox
fn Sandbox(&self, can_gc: CanGc) -> DomRoot<DOMTokenList> {
self.sandbox.or_init(|| {
DOMTokenList::new(
self.upcast::<Element>(),
&local_name!("sandbox"),
Some(vec![
Atom::from("allow-same-origin"),
Atom::from("allow-forms"),
Atom::from("allow-pointer-lock"),
Atom::from("allow-popups"),
Atom::from("allow-scripts"),
Atom::from("allow-top-navigation"),
]),
can_gc,
)
})
}
// https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow
fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> {
self.browsing_context_id
.get()
.and_then(ScriptThread::find_window_proxy)
}
// https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument
// https://html.spec.whatwg.org/multipage/#concept-bcc-content-document
fn GetContentDocument(&self) -> Option<DomRoot<Document>> {
// Step 1.
let pipeline_id = self.pipeline_id.get()?;
// Step 2-3.
// Note that this lookup will fail if the document is dissimilar-origin,
// so we should return None in that case.
let document = ScriptThread::find_document(pipeline_id)?;
// Step 4.
let current = GlobalScope::current()
.expect("No current global object")
.as_window()
.Document();
if !current.origin().same_origin_domain(document.origin()) {
return None;
}
// Step 5.
Some(document)
}
/// <https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy>
fn ReferrerPolicy(&self) -> DOMString {
reflect_referrer_policy_attribute(self.upcast::<Element>())
}
// https://html.spec.whatwg.org/multipage/#attr-iframe-referrerpolicy
make_setter!(SetReferrerPolicy, "referrerpolicy");
// https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
make_bool_getter!(AllowFullscreen, "allowfullscreen");
// https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
// https://html.spec.whatwg.org/multipage/#dom-dim-width
make_getter!(Width, "width");
// https://html.spec.whatwg.org/multipage/#dom-dim-width
make_dimension_setter!(SetWidth, "width");
// https://html.spec.whatwg.org/multipage/#dom-dim-height
make_getter!(Height, "height");
// https://html.spec.whatwg.org/multipage/#dom-dim-height
make_dimension_setter!(SetHeight, "height");
// https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
make_getter!(FrameBorder, "frameborder");
// https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder
make_setter!(SetFrameBorder, "frameborder");
// https://html.spec.whatwg.org/multipage/#dom-iframe-name
// A child browsing context checks the name of its iframe only at the time
// it is created; subsequent name sets have no special effect.
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-iframe-name
// This is specified as reflecting the name content attribute of the
// element, not the name of the child browsing context.
make_getter!(Name, "name");
}
impl VirtualMethods for HTMLIFrameElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("sandbox") => {
self.sandbox_allowance
.set(mutation.new_value(attr).map(|value| {
let mut modes = SandboxAllowance::ALLOW_NOTHING;
for token in value.as_tokens() {
modes |= match &*token.to_ascii_lowercase() {
"allow-same-origin" => SandboxAllowance::ALLOW_SAME_ORIGIN,
"allow-forms" => SandboxAllowance::ALLOW_FORMS,
"allow-pointer-lock" => SandboxAllowance::ALLOW_POINTER_LOCK,
"allow-popups" => SandboxAllowance::ALLOW_POPUPS,
"allow-scripts" => SandboxAllowance::ALLOW_SCRIPTS,
"allow-top-navigation" => SandboxAllowance::ALLOW_TOP_NAVIGATION,
_ => SandboxAllowance::ALLOW_NOTHING,
};
}
modes
}));
},
local_name!("srcdoc") => {
// https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9
// "Whenever an iframe element with a non-null nested browsing context has its
// srcdoc attribute set, changed, or removed, the user agent must process the
// iframe attributes."
// but we can't check that directly, since the child browsing context
// may be in a different script thread. Instead, we check to see if the parent
// is in a document tree and has a browsing context, which is what causes
// the child browsing context to be created.
// trigger the processing of iframe attributes whenever "srcdoc" attribute is set, changed or removed
if self.upcast::<Node>().is_connected_with_browsing_context() {
debug!("iframe srcdoc modified while in browsing context.");
self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
}
},
local_name!("src") => {
// https://html.spec.whatwg.org/multipage/#the-iframe-element
// "Similarly, whenever an iframe element with a non-null nested browsing context
// but with no srcdoc attribute specified has its src attribute set, changed, or removed,
// the user agent must process the iframe attributes,"
// but we can't check that directly, since the child browsing context
// may be in a different script thread. Instead, we check to see if the parent
// is in a document tree and has a browsing context, which is what causes
// the child browsing context to be created.
if self.upcast::<Node>().is_connected_with_browsing_context() {
debug!("iframe src set while in browsing context.");
self.process_the_iframe_attributes(ProcessingMode::NotFirstTime, can_gc);
}
},
_ => {},
}
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()),
local_name!("width") => AttrValue::from_dimension(value.into()),
local_name!("height") => AttrValue::from_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn post_connection_steps(&self) {
if let Some(s) = self.super_type() {
s.post_connection_steps();
}
// https://html.spec.whatwg.org/multipage/#the-iframe-element
// "When an iframe element is inserted into a document that has
// a browsing context, the user agent must create a new
// browsing context, set the element's nested browsing context
// to the newly-created browsing context, and then process the
// iframe attributes for the "first time"."
if self.upcast::<Node>().is_connected_with_browsing_context() {
debug!("iframe bound to browsing context.");
self.create_nested_browsing_context(CanGc::note());
self.process_the_iframe_attributes(ProcessingMode::FirstTime, CanGc::note());
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.owner_document().invalidate_iframes_collection();
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
let blocker = &self.load_blocker;
LoadBlocker::terminate(blocker, CanGc::note());
// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
let window = self.owner_window();
let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
// Ask the constellation to remove the iframe, and tell us the
// pipeline ids of the closed pipelines.
let browsing_context_id = match self.browsing_context_id() {
None => return warn!("Unbinding already unbound iframe."),
Some(id) => id,
};
debug!("Unbinding frame {}.", browsing_context_id);
let msg = ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, sender);
window
.as_global_scope()
.script_to_constellation_chan()
.send(msg)
.unwrap();
let exited_pipeline_ids = receiver.recv().unwrap();
// The spec for discarding is synchronous,
// so we need to discard the browsing contexts now, rather than
// when the `PipelineExit` message arrives.
for exited_pipeline_id in exited_pipeline_ids {
// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
if let Some(exited_document) = ScriptThread::find_document(exited_pipeline_id) {
debug!(
"Discarding browsing context for pipeline {}",
exited_pipeline_id
);
let exited_window = exited_document.window();
exited_window.discard_browsing_context();
for exited_iframe in exited_document.iframes().iter() {
debug!("Discarding nested browsing context");
exited_iframe.destroy_nested_browsing_context();
}
}
}
// Resetting the pipeline_id to None is required here so that
// if this iframe is subsequently re-added to the document
// the load doesn't think that it's a navigation, but instead
// a new iframe. Without this, the constellation gets very
// confused.
self.destroy_nested_browsing_context();
self.owner_document().invalidate_iframes_collection();
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::AttrValue;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement};
use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLLabelElement {
htmlelement: HTMLElement,
}
impl HTMLLabelElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLLabelElement {
HTMLLabelElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLLabelElement> {
Node::reflect_node_with_proto(
Box::new(HTMLLabelElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl Activatable for HTMLLabelElement {
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
true
}
// https://html.spec.whatwg.org/multipage/#the-label-element:activation_behaviour
// Basically this is telling us that if activation bubbles up to the label
// at all, we are free to do an implementation-dependent thing;
// firing a click event is an example, and the precise details of that
// click event (e.g. isTrusted) are not specified.
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
if let Some(e) = self.GetControl() {
e.Click(can_gc);
}
}
}
impl HTMLLabelElementMethods<crate::DomTypeHolder> for HTMLLabelElement {
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
make_getter!(HtmlFor, "for");
// https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
make_atomic_setter!(SetHtmlFor, "for");
// https://html.spec.whatwg.org/multipage/#dom-label-control
fn GetControl(&self) -> Option<DomRoot<HTMLElement>> {
let for_attr = match self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("for"))
{
Some(for_attr) => for_attr,
None => return self.first_labelable_descendant(),
};
let for_value = for_attr.Value();
// "If the attribute is specified and there is an element in the tree
// whose ID is equal to the value of the for attribute, and the first
// such element in tree order is a labelable element, then that
// element is the label element's labeled control."
// Two subtle points here: we need to search the _tree_, which is
// not necessarily the document if we're detached from the document,
// and we only consider one element even if a later element with
// the same ID is labelable.
let maybe_found = self
.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty())
.traverse_preorder(ShadowIncluding::No)
.find_map(|e| {
if let Some(htmle) = e.downcast::<HTMLElement>() {
if htmle.upcast::<Element>().Id() == for_value {
Some(DomRoot::from_ref(htmle))
} else {
None
}
} else {
None
}
});
// We now have the element that we would return, but only return it
// if it's labelable.
if let Some(ref maybe_labelable) = maybe_found {
if maybe_labelable.is_labelable_element() {
return maybe_found;
}
}
None
}
}
impl VirtualMethods for HTMLLabelElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("for") => AttrValue::from_atomic(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if *attr.local_name() == local_name!("form") {
self.form_attribute_mutated(mutation, can_gc);
}
}
}
impl HTMLLabelElement {
pub(crate) fn first_labelable_descendant(&self) -> Option<DomRoot<HTMLElement>> {
self.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLElement>)
.find(|elem| elem.is_labelable_element())
}
}
impl FormControl for HTMLLabelElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.GetControl()
.map(DomRoot::upcast::<Element>)
.and_then(|elem| {
elem.as_maybe_form_control()
.and_then(|control| control.form_owner())
})
}
fn set_form_owner(&self, _: Option<&HTMLFormElement>) {
// Label is a special case for form owner, it reflects its control's
// form owner. Therefore it doesn't hold form owner itself.
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}

View file

@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLLegendElementBinding::HTMLLegendElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::node::{BindContext, Node, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLLegendElement {
htmlelement: HTMLElement,
form_owner: MutNullableDom<HTMLFormElement>,
}
impl HTMLLegendElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLLegendElement {
HTMLLegendElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
form_owner: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLLegendElement> {
Node::reflect_node_with_proto(
Box::new(HTMLLegendElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl VirtualMethods for HTMLLegendElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.upcast::<Element>()
.check_ancestors_disabled_state_for_form_control();
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
if node
.ancestors()
.any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
{
el.check_ancestors_disabled_state_for_form_control();
} else {
el.check_disabled_attribute();
}
}
}
impl HTMLLegendElementMethods<crate::DomTypeHolder> for HTMLLegendElement {
// https://html.spec.whatwg.org/multipage/#dom-legend-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
let parent = self.upcast::<Node>().GetParentElement()?;
if parent.is::<HTMLFieldSetElement>() {
return self.form_owner();
}
None
}
}
impl FormControl for HTMLLegendElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}

View file

@ -0,0 +1,75 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use style::attr::AttrValue;
use crate::dom::bindings::codegen::Bindings::HTMLLIElementBinding::HTMLLIElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLLIElement {
htmlelement: HTMLElement,
}
impl HTMLLIElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLLIElement {
HTMLLIElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLLIElement> {
Node::reflect_node_with_proto(
Box::new(HTMLLIElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLLIElementMethods<crate::DomTypeHolder> for HTMLLIElement {
// https://html.spec.whatwg.org/multipage/#dom-li-value
make_int_getter!(Value, "value");
// https://html.spec.whatwg.org/multipage/#dom-li-value
make_int_setter!(SetValue, "value");
}
impl VirtualMethods for HTMLLIElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("value") => AttrValue::from_i32(value.into(), 0),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlareaelement::HTMLAreaElement;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{Node, ShadowIncluding};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLMapElement {
htmlelement: HTMLElement,
}
impl HTMLMapElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLMapElement {
HTMLMapElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLMapElement> {
Node::reflect_node_with_proto(
Box::new(HTMLMapElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
pub(crate) fn get_area_elements(&self) -> Vec<DomRoot<HTMLAreaElement>> {
self.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLAreaElement>)
.collect()
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLMenuElementBinding::HTMLMenuElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLMenuElement {
htmlelement: HTMLElement,
}
impl HTMLMenuElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLMenuElement {
HTMLMenuElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLMenuElement> {
Node::reflect_node_with_proto(
Box::new(HTMLMenuElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLMenuElementMethods<crate::DomTypeHolder> for HTMLMenuElement {
// spec just mandates that compact reflects the content attribute,
// with no other semantics. Layout could use it to
// change line spacing, but nothing requires it to do so.
// https://html.spec.whatwg.org/multipage/#dom-menu-compact
make_bool_setter!(SetCompact, "compact");
// https://html.spec.whatwg.org/multipage/#dom-menu-compact
make_bool_getter!(Compact, "compact");
}

View file

@ -0,0 +1,224 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::str::FromStr;
use compositing_traits::viewport_description::ViewportDescription;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::str::HTML_SPACE_CHARACTERS;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::{Document, determine_policy_for_token};
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlheadelement::HTMLHeadElement;
use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLMetaElement {
htmlelement: HTMLElement,
}
impl HTMLMetaElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLMetaElement {
HTMLMetaElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLMetaElement> {
Node::reflect_node_with_proto(
Box::new(HTMLMetaElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
fn process_attributes(&self) {
let element = self.upcast::<Element>();
if let Some(ref name) = element.get_name() {
let name = name.to_ascii_lowercase();
let name = name.trim_matches(HTML_SPACE_CHARACTERS);
if name == "referrer" {
self.apply_referrer();
}
if name == "viewport" {
self.parse_and_send_viewport_if_necessary();
}
// https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv
} else if !self.HttpEquiv().is_empty() {
// TODO: Implement additional http-equiv candidates
match self.HttpEquiv().to_ascii_lowercase().as_str() {
"refresh" => self.declarative_refresh(),
"content-security-policy" => self.apply_csp_list(),
_ => {},
}
}
}
fn process_referrer_attribute(&self) {
let element = self.upcast::<Element>();
if let Some(ref name) = element.get_name() {
let name = name.to_ascii_lowercase();
let name = name.trim_matches(HTML_SPACE_CHARACTERS);
if name == "referrer" {
self.apply_referrer();
}
}
}
/// <https://html.spec.whatwg.org/multipage/#meta-referrer>
fn apply_referrer(&self) {
let doc = self.owner_document();
// From spec: For historical reasons, unlike other standard metadata names, the processing model for referrer
// is not responsive to element removals, and does not use tree order. Only the most-recently-inserted or
// most-recently-modified meta element in this state has an effect.
// 1. If element is not in a document tree, then return.
let meta_node = self.upcast::<Node>();
if !meta_node.is_in_a_document_tree() {
return;
}
// 2. If element does not have a name attribute whose value is an ASCII case-insensitive match for "referrer",
// then return.
if self.upcast::<Element>().get_name() != Some(atom!("referrer")) {
return;
}
// 3. If element does not have a content attribute, or that attribute's value is the empty string, then return.
let content = self
.upcast::<Element>()
.get_attribute(&ns!(), &local_name!("content"));
if let Some(attr) = content {
let attr = attr.value();
let attr_val = attr.trim();
if !attr_val.is_empty() {
doc.set_referrer_policy(determine_policy_for_token(attr_val));
}
}
}
/// <https://drafts.csswg.org/css-viewport/#parsing-algorithm>
fn parse_and_send_viewport_if_necessary(&self) {
// Skip processing if this isn't the top level frame
if !self.owner_window().is_top_level() {
return;
}
let element = self.upcast::<Element>();
let Some(content) = element.get_attribute(&ns!(), &local_name!("content")) else {
return;
};
if let Ok(viewport) = ViewportDescription::from_str(&content.value()) {
self.owner_window()
.compositor_api()
.viewport(self.owner_window().webview_id(), viewport);
}
}
/// <https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-content-security-policy>
fn apply_csp_list(&self) {
if let Some(parent) = self.upcast::<Node>().GetParentElement() {
if let Some(head) = parent.downcast::<HTMLHeadElement>() {
head.set_content_security_policy();
}
}
}
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
fn declarative_refresh(&self) {
if !self.upcast::<Node>().is_in_a_document_tree() {
return;
}
// 2
let content = self.Content();
// 1
if !content.is_empty() {
// 3
self.owner_document()
.shared_declarative_refresh_steps(content.as_bytes());
}
}
}
impl HTMLMetaElementMethods<crate::DomTypeHolder> for HTMLMetaElement {
// https://html.spec.whatwg.org/multipage/#dom-meta-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-meta-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-meta-content
make_getter!(Content, "content");
// https://html.spec.whatwg.org/multipage/#dom-meta-content
make_setter!(SetContent, "content");
// https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
make_getter!(HttpEquiv, "http-equiv");
// https://html.spec.whatwg.org/multipage/#dom-meta-httpequiv
make_atomic_setter!(SetHttpEquiv, "http-equiv");
// https://html.spec.whatwg.org/multipage/#dom-meta-scheme
make_getter!(Scheme, "scheme");
// https://html.spec.whatwg.org/multipage/#dom-meta-scheme
make_setter!(SetScheme, "scheme");
}
impl VirtualMethods for HTMLMetaElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
if context.tree_connected {
self.process_attributes();
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.attribute_mutated(attr, mutation, can_gc);
}
self.process_referrer_attribute();
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.unbind_from_tree(context, can_gc);
}
if context.tree_connected {
self.process_referrer_attribute();
}
}
}

View file

@ -0,0 +1,349 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Ref;
use std::ops::{Add, Div};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use stylo_dom::ElementState;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::HTMLMeterElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmldivelement::HTMLDivElement;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeTraits};
use crate::dom::nodelist::NodeList;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLMeterElement {
htmlelement: HTMLElement,
labels_node_list: MutNullableDom<NodeList>,
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
/// Holds handles to all slots in the UA shadow tree
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ShadowTree {
meter_value: Dom<HTMLDivElement>,
}
/// <https://html.spec.whatwg.org/multipage/#the-meter-element>
impl HTMLMeterElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLMeterElement {
HTMLMeterElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
labels_node_list: MutNullableDom::new(None),
shadow_tree: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLMeterElement> {
Node::reflect_node_with_proto(
Box::new(HTMLMeterElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn create_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let root = self.upcast::<Element>().attach_ua_shadow_root(true, can_gc);
let meter_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
root.upcast::<Node>()
.AppendChild(meter_value.upcast::<Node>(), can_gc)
.unwrap();
let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
meter_value: meter_value.as_traced(),
});
self.upcast::<Node>()
.dirty(crate::dom::node::NodeDamage::Other);
}
fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
if !self.upcast::<Element>().is_shadow_host() {
self.create_shadow_tree(can_gc);
}
Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
.ok()
.expect("UA shadow tree was not created")
}
fn update_state(&self, can_gc: CanGc) {
let value = *self.Value();
let low = *self.Low();
let high = *self.High();
let min = *self.Min();
let max = *self.Max();
let optimum = *self.Optimum();
// If the optimum point is less than the low boundary, then the region between the minimum value and
// the low boundary must be treated as the optimum region, the region from the low boundary up to the
// high boundary must be treated as a suboptimal region, and the remaining region must be treated as
// an even less good region
let element_state = if optimum < low {
if value < low {
ElementState::OPTIMUM
} else if value <= high {
ElementState::SUB_OPTIMUM
} else {
ElementState::SUB_SUB_OPTIMUM
}
}
// If the optimum point is higher than the high boundary, then the situation is reversed; the region between
// the high boundary and the maximum value must be treated as the optimum region, the region from the high
// boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must
// be treated as an even less good region.
else if optimum > high {
if value > high {
ElementState::OPTIMUM
} else if value >= low {
ElementState::SUB_OPTIMUM
} else {
ElementState::SUB_SUB_OPTIMUM
}
}
// If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them,
// then the region between the low and high boundaries of the gauge must be treated as the optimum region,
// and the low and high parts, if any, must be treated as suboptimal.
else if (low..=high).contains(&value) {
ElementState::OPTIMUM
} else {
ElementState::SUB_OPTIMUM
};
// Set the correct pseudo class
self.upcast::<Element>()
.set_state(ElementState::METER_OPTIMUM_STATES, false);
self.upcast::<Element>().set_state(element_state, true);
// Update the visual width of the meter
let shadow_tree = self.shadow_tree(can_gc);
let position = (value - min) / (max - min) * 100.0;
let style = format!("width: {position}%");
shadow_tree
.meter_value
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
}
}
impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
/// <https://html.spec.whatwg.org/multipage/#concept-meter-actual>
fn Value(&self) -> Finite<f64> {
let min = *self.Min();
let max = *self.Max();
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("value"))
.parse_floating_point_number()
.map_or(0.0, |candidate_actual_value| {
candidate_actual_value.clamp(min, max)
}),
)
}
/// <https://html.spec.whatwg.org/multipage/#dom-meter-value>
fn SetValue(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>()
.set_string_attribute(&local_name!("value"), string_value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-minimum>
fn Min(&self) -> Finite<f64> {
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("min"))
.parse_floating_point_number()
.unwrap_or(0.0),
)
}
/// <https://html.spec.whatwg.org/multipage/#dom-meter-min>
fn SetMin(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>()
.set_string_attribute(&local_name!("min"), string_value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
fn Max(&self) -> Finite<f64> {
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("max"))
.parse_floating_point_number()
.unwrap_or(1.0)
.max(*self.Min()),
)
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
fn SetMax(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>()
.set_string_attribute(&local_name!("max"), string_value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-low>
fn Low(&self) -> Finite<f64> {
let min = *self.Min();
let max = *self.Max();
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("low"))
.parse_floating_point_number()
.map_or(min, |candidate_low_boundary| {
candidate_low_boundary.clamp(min, max)
}),
)
}
/// <https://html.spec.whatwg.org/multipage/#dom-meter-low>
fn SetLow(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>()
.set_string_attribute(&local_name!("low"), string_value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-high>
fn High(&self) -> Finite<f64> {
let max: f64 = *self.Max();
let low: f64 = *self.Low();
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("high"))
.parse_floating_point_number()
.map_or(max, |candidate_high_boundary| {
if candidate_high_boundary < low {
return low;
}
candidate_high_boundary.clamp(*self.Min(), max)
}),
)
}
/// <https://html.spec.whatwg.org/multipage/#dom-meter-high>
fn SetHigh(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>()
.set_string_attribute(&local_name!("high"), string_value, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#concept-meter-optimum>
fn Optimum(&self) -> Finite<f64> {
let max = *self.Max();
let min = *self.Min();
Finite::wrap(
self.upcast::<Element>()
.get_string_attribute(&local_name!("optimum"))
.parse_floating_point_number()
.map_or(max.add(min).div(2.0), |candidate_optimum_point| {
candidate_optimum_point.clamp(min, max)
}),
)
}
/// <https://html.spec.whatwg.org/multipage/#dom-meter-optimum>
fn SetOptimum(&self, value: Finite<f64>, can_gc: CanGc) {
let mut string_value = DOMString::from_string((*value).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>().set_string_attribute(
&local_name!("optimum"),
string_value,
can_gc,
);
}
}
impl VirtualMethods for HTMLMeterElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
let is_important_attribute = matches!(
attr.local_name(),
&local_name!("high") |
&local_name!("low") |
&local_name!("min") |
&local_name!("max") |
&local_name!("optimum") |
&local_name!("value")
);
if is_important_attribute {
self.update_state(CanGc::note());
}
}
fn children_changed(&self, mutation: &ChildrenMutation) {
self.super_type().unwrap().children_changed(mutation);
self.update_state(CanGc::note());
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
self.super_type().unwrap().bind_to_tree(context, can_gc);
self.update_state(CanGc::note());
}
}

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLModElement {
htmlelement: HTMLElement,
}
impl HTMLModElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLModElement {
HTMLModElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLModElement> {
Node::reflect_node_with_proto(
Box::new(HTMLModElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,186 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use pixels::RasterImage;
use servo_arc::Arc;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::node::{Node, NodeTraits};
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidityState;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLObjectElement {
htmlelement: HTMLElement,
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
image: DomRefCell<Option<Arc<RasterImage>>>,
form_owner: MutNullableDom<HTMLFormElement>,
validity_state: MutNullableDom<ValidityState>,
}
impl HTMLObjectElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLObjectElement {
HTMLObjectElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
image: DomRefCell::new(None),
form_owner: Default::default(),
validity_state: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLObjectElement> {
Node::reflect_node_with_proto(
Box::new(HTMLObjectElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
trait ProcessDataURL {
fn process_data_url(&self);
}
impl ProcessDataURL for &HTMLObjectElement {
// Makes the local `data` member match the status of the `data` attribute and starts
/// prefetching the image. This method must be called after `data` is changed.
fn process_data_url(&self) {
let element = self.upcast::<Element>();
// TODO: support other values
if let (None, Some(_uri)) = (
element.get_attribute(&ns!(), &local_name!("type")),
element.get_attribute(&ns!(), &local_name!("data")),
) {
// TODO(gw): Prefetch the image here.
}
}
}
impl HTMLObjectElementMethods<crate::DomTypeHolder> for HTMLObjectElement {
// https://html.spec.whatwg.org/multipage/#dom-object-type
make_getter!(Type, "type");
// https://html.spec.whatwg.org/multipage/#dom-object-type
make_setter!(SetType, "type");
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
}
impl Validatable for HTMLObjectElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#the-object-element%3Abarred-from-constraint-validation
false
}
}
impl VirtualMethods for HTMLObjectElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("data") => {
if let AttributeMutation::Set(_) = mutation {
self.process_data_url();
}
},
local_name!("form") => {
self.form_attribute_mutated(mutation, can_gc);
},
_ => {},
}
}
}
impl FormControl for HTMLObjectElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLOListElement {
htmlelement: HTMLElement,
}
impl HTMLOListElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLOListElement {
HTMLOListElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLOListElement> {
Node::reflect_node_with_proto(
Box::new(HTMLOListElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,154 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use script_bindings::str::DOMString;
use stylo_dom::ElementState;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods;
use crate::dom::bindings::codegen::GenericBindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmloptionelement::HTMLOptionElement;
use crate::dom::html::htmlselectelement::HTMLSelectElement;
use crate::dom::node::{BindContext, Node, UnbindContext};
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
/// <https://html.spec.whatwg.org/multipage/#htmloptgroupelement>
#[dom_struct]
pub(crate) struct HTMLOptGroupElement {
htmlelement: HTMLElement,
}
impl HTMLOptGroupElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLOptGroupElement {
HTMLOptGroupElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED,
local_name,
prefix,
document,
),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLOptGroupElement> {
Node::reflect_node_with_proto(
Box::new(HTMLOptGroupElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn update_select_validity(&self, can_gc: CanGc) {
if let Some(select) = self.owner_select_element() {
select
.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
}
fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
self.upcast::<Node>()
.GetParentNode()
.and_then(DomRoot::downcast)
}
}
impl HTMLOptGroupElementMethods<crate::DomTypeHolder> for HTMLOptGroupElement {
// https://html.spec.whatwg.org/multipage/#dom-optgroup-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-optgroup-disabled
make_bool_setter!(SetDisabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-optgroup-label
make_getter!(Label, "label");
// https://html.spec.whatwg.org/multipage/#dom-optgroup-label
make_setter!(SetLabel, "label");
}
impl VirtualMethods for HTMLOptGroupElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if attr.local_name() == &local_name!("disabled") {
let disabled_state = match mutation {
AttributeMutation::Set(None) => true,
AttributeMutation::Set(Some(_)) => {
// Option group was already disabled.
return;
},
AttributeMutation::Removed => false,
};
let el = self.upcast::<Element>();
el.set_disabled_state(disabled_state);
el.set_enabled_state(!disabled_state);
let options = el
.upcast::<Node>()
.children()
.filter(|child| child.is::<HTMLOptionElement>())
.map(|child| DomRoot::from_ref(child.downcast::<HTMLOptionElement>().unwrap()));
if disabled_state {
for option in options {
let el = option.upcast::<Element>();
el.set_disabled_state(true);
el.set_enabled_state(false);
}
} else {
for option in options {
let el = option.upcast::<Element>();
el.check_disabled_attribute();
}
}
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(super_type) = self.super_type() {
super_type.bind_to_tree(context, can_gc);
}
self.update_select_validity(can_gc);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
if let Some(select) = context.parent.downcast::<HTMLSelectElement>() {
select
.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
}
}

View file

@ -0,0 +1,409 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::convert::TryInto;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, QualName, local_name, ns};
use js::rust::HandleObject;
use style::str::{split_html_space_chars, str_join};
use stylo_dom::ElementState;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElement_Binding::HTMLSelectElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::characterdata::CharacterData;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlformelement::HTMLFormElement;
use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
use crate::dom::html::htmlscriptelement::HTMLScriptElement;
use crate::dom::html::htmlselectelement::HTMLSelectElement;
use crate::dom::node::{BindContext, ChildrenMutation, Node, ShadowIncluding, UnbindContext};
use crate::dom::text::Text;
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLOptionElement {
htmlelement: HTMLElement,
/// <https://html.spec.whatwg.org/multipage/#attr-option-selected>
selectedness: Cell<bool>,
/// <https://html.spec.whatwg.org/multipage/#concept-option-dirtiness>
dirtiness: Cell<bool>,
}
impl HTMLOptionElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLOptionElement {
HTMLOptionElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED,
local_name,
prefix,
document,
),
selectedness: Cell::new(false),
dirtiness: Cell::new(false),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLOptionElement> {
Node::reflect_node_with_proto(
Box::new(HTMLOptionElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn set_selectedness(&self, selected: bool) {
self.selectedness.set(selected);
}
pub(crate) fn set_dirtiness(&self, dirtiness: bool) {
self.dirtiness.set(dirtiness);
}
fn pick_if_selected_and_reset(&self) {
if let Some(select) = self.owner_select_element() {
if self.Selected() {
select.pick_option(self);
}
select.ask_for_reset();
}
}
// https://html.spec.whatwg.org/multipage/#concept-option-index
fn index(&self) -> i32 {
let Some(owner_select) = self.owner_select_element() else {
return 0;
};
let Some(position) = owner_select.list_of_options().position(|n| &*n == self) else {
// An option should always be in it's owner's list of options, but it's not worth a browser panic
warn!("HTMLOptionElement called index_in_select at a select that did not contain it");
return 0;
};
position.try_into().unwrap_or(0)
}
fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
let parent = self.upcast::<Node>().GetParentNode()?;
if parent.is::<HTMLOptGroupElement>() {
DomRoot::downcast::<HTMLSelectElement>(parent.GetParentNode()?)
} else {
DomRoot::downcast::<HTMLSelectElement>(parent)
}
}
fn update_select_validity(&self, can_gc: CanGc) {
if let Some(select) = self.owner_select_element() {
select
.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
}
/// <https://html.spec.whatwg.org/multipage/#concept-option-label>
///
/// Note that this is not equivalent to <https://html.spec.whatwg.org/multipage/#dom-option-label>.
pub(crate) fn displayed_label(&self) -> DOMString {
// > The label of an option element is the value of the label content attribute, if there is one
// > and its value is not the empty string, or, otherwise, the value of the element's text IDL attribute.
let label = self
.upcast::<Element>()
.get_string_attribute(&local_name!("label"));
if label.is_empty() {
return self.Text();
}
label
}
}
impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option>
fn Option(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
text: DOMString,
value: Option<DOMString>,
default_selected: bool,
selected: bool,
) -> Fallible<DomRoot<HTMLOptionElement>> {
let element = Element::create(
QualName::new(None, ns!(html), local_name!("option")),
None,
&window.Document(),
ElementCreator::ScriptCreated,
CustomElementCreationMode::Synchronous,
proto,
can_gc,
);
let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
if !text.is_empty() {
option
.upcast::<Node>()
.set_text_content_for_element(Some(text), can_gc)
}
if let Some(val) = value {
option.SetValue(val)
}
option.SetDefaultSelected(default_selected);
option.set_selectedness(selected);
option.update_select_validity(can_gc);
Ok(option)
}
// https://html.spec.whatwg.org/multipage/#dom-option-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-option-disabled
make_bool_setter!(SetDisabled, "disabled");
/// <https://html.spec.whatwg.org/multipage/#dom-option-text>
fn Text(&self) -> DOMString {
let mut content = DOMString::new();
let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
while let Some(node) = iterator.peek() {
if let Some(element) = node.downcast::<Element>() {
let html_script = element.is::<HTMLScriptElement>();
let svg_script = *element.namespace() == ns!(svg) &&
element.local_name() == &local_name!("script");
if html_script || svg_script {
iterator.next_skipping_children();
continue;
}
}
if node.is::<Text>() {
let characterdata = node.downcast::<CharacterData>().unwrap();
content.push_str(&characterdata.Data());
}
iterator.next();
}
DOMString::from(str_join(split_html_space_chars(&content), " "))
}
/// <https://html.spec.whatwg.org/multipage/#dom-option-text>
fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
}
/// <https://html.spec.whatwg.org/multipage/#dom-option-form>
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
let parent = self.upcast::<Node>().GetParentNode().and_then(|p| {
if p.is::<HTMLOptGroupElement>() {
p.upcast::<Node>().GetParentNode()
} else {
Some(p)
}
});
parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm()))
}
/// <https://html.spec.whatwg.org/multipage/#attr-option-value>
fn Value(&self) -> DOMString {
let element = self.upcast::<Element>();
let attr = &local_name!("value");
if element.has_attribute(attr) {
element.get_string_attribute(attr)
} else {
self.Text()
}
}
// https://html.spec.whatwg.org/multipage/#attr-option-value
make_setter!(SetValue, "value");
/// <https://html.spec.whatwg.org/multipage/#attr-option-label>
fn Label(&self) -> DOMString {
let element = self.upcast::<Element>();
let attr = &local_name!("label");
if element.has_attribute(attr) {
element.get_string_attribute(attr)
} else {
self.Text()
}
}
// https://html.spec.whatwg.org/multipage/#attr-option-label
make_setter!(SetLabel, "label");
// https://html.spec.whatwg.org/multipage/#dom-option-defaultselected
make_bool_getter!(DefaultSelected, "selected");
// https://html.spec.whatwg.org/multipage/#dom-option-defaultselected
make_bool_setter!(SetDefaultSelected, "selected");
/// <https://html.spec.whatwg.org/multipage/#dom-option-selected>
fn Selected(&self) -> bool {
self.selectedness.get()
}
/// <https://html.spec.whatwg.org/multipage/#dom-option-selected>
fn SetSelected(&self, selected: bool) {
self.dirtiness.set(true);
self.selectedness.set(selected);
self.pick_if_selected_and_reset();
self.update_select_validity(CanGc::note());
}
/// <https://html.spec.whatwg.org/multipage/#dom-option-index>
fn Index(&self) -> i32 {
self.index()
}
}
impl VirtualMethods for HTMLOptionElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("disabled") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(_) => {
el.set_disabled_state(true);
el.set_enabled_state(false);
},
AttributeMutation::Removed => {
el.set_disabled_state(false);
el.set_enabled_state(true);
el.check_parent_disabled_state_for_option();
},
}
self.update_select_validity(can_gc);
},
local_name!("selected") => {
match mutation {
AttributeMutation::Set(_) => {
// https://html.spec.whatwg.org/multipage/#concept-option-selectedness
if !self.dirtiness.get() {
self.selectedness.set(true);
}
},
AttributeMutation::Removed => {
// https://html.spec.whatwg.org/multipage/#concept-option-selectedness
if !self.dirtiness.get() {
self.selectedness.set(false);
}
},
}
self.update_select_validity(can_gc);
},
local_name!("label") => {
// The label of the selected option is displayed inside the select element, so we need to repaint
// when it changes
if let Some(select_element) = self.owner_select_element() {
select_element.update_shadow_tree(CanGc::note());
}
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.upcast::<Element>()
.check_parent_disabled_state_for_option();
self.pick_if_selected_and_reset();
self.update_select_validity(can_gc);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
if let Some(select) = context
.parent
.inclusive_ancestors(ShadowIncluding::No)
.filter_map(DomRoot::downcast::<HTMLSelectElement>)
.next()
{
select
.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
select.ask_for_reset();
}
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
if node.GetParentNode().is_some() {
el.check_parent_disabled_state_for_option();
} else {
el.check_disabled_attribute();
}
}
fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(super_type) = self.super_type() {
super_type.children_changed(mutation);
}
// Changing the descendants of a selected option can change it's displayed label
// if it does not have a label attribute
if !self
.upcast::<Element>()
.has_attribute(&local_name!("label"))
{
if let Some(owner_select) = self.owner_select_element() {
if owner_select
.selected_option()
.is_some_and(|selected_option| self == &*selected_option)
{
owner_select.update_shadow_tree(CanGc::note());
}
}
}
}
}

View file

@ -0,0 +1,250 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::Ordering;
use dom_struct::dom_struct;
use html5ever::local_name;
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::UnionTypes::{
HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement,
};
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::element::Element;
use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
use crate::dom::html::htmloptionelement::HTMLOptionElement;
use crate::dom::html::htmlselectelement::HTMLSelectElement;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLOptionsCollection {
collection: HTMLCollection,
}
impl HTMLOptionsCollection {
fn new_inherited(
select: &HTMLSelectElement,
filter: Box<dyn CollectionFilter + 'static>,
) -> HTMLOptionsCollection {
HTMLOptionsCollection {
collection: HTMLCollection::new_inherited(select.upcast(), filter),
}
}
pub(crate) fn new(
window: &Window,
select: &HTMLSelectElement,
filter: Box<dyn CollectionFilter + 'static>,
can_gc: CanGc,
) -> DomRoot<HTMLOptionsCollection> {
reflect_dom_object(
Box::new(HTMLOptionsCollection::new_inherited(select, filter)),
window,
can_gc,
)
}
fn add_new_elements(&self, count: u32, can_gc: CanGc) -> ErrorResult {
let root = self.upcast().root_node();
let document = root.owner_document();
for _ in 0..count {
let element =
HTMLOptionElement::new(local_name!("option"), None, &document, None, can_gc);
let node = element.upcast::<Node>();
root.AppendChild(node, can_gc)?;
}
Ok(())
}
}
impl HTMLOptionsCollectionMethods<crate::DomTypeHolder> for HTMLOptionsCollection {
// FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
// HTMLOptionsCollection) implements NamedGetter.
// https://github.com/servo/servo/issues/5875
//
// https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> {
self.upcast().NamedItem(name)
}
// https://heycam.github.io/webidl/#dfn-supported-property-names
fn SupportedPropertyNames(&self) -> Vec<DOMString> {
self.upcast().SupportedPropertyNames()
}
// FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of
// HTMLOptionsCollection) implements IndexedGetter.
// https://github.com/servo/servo/issues/5875
//
// https://dom.spec.whatwg.org/#dom-htmlcollection-item
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
self.upcast().IndexedGetter(index)
}
// https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-setter
fn IndexedSetter(
&self,
index: u32,
value: Option<&HTMLOptionElement>,
can_gc: CanGc,
) -> ErrorResult {
if let Some(value) = value {
// Step 2
let length = self.upcast().Length();
// Step 3
let n = index as i32 - length as i32;
// Step 4
if n > 0 {
self.add_new_elements(n as u32, can_gc)?;
}
// Step 5
let node = value.upcast::<Node>();
let root = self.upcast().root_node();
if n >= 0 {
Node::pre_insert(node, &root, None, can_gc).map(|_| ())
} else {
let child = self.upcast().IndexedGetter(index).unwrap();
let child_node = child.upcast::<Node>();
root.ReplaceChild(node, child_node, can_gc).map(|_| ())
}
} else {
// Step 1
self.Remove(index as i32);
Ok(())
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
fn Length(&self) -> u32 {
self.upcast().Length()
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-length>
fn SetLength(&self, length: u32, can_gc: CanGc) {
// Step 1. Let current be the number of nodes represented by the collection.
let current = self.upcast().Length();
match length.cmp(&current) {
// Step 2. If the given value is greater than current, then:
Ordering::Greater => {
// Step 2.1 If the given value is greater than 100,000, then return.
if length > 100_000 {
return;
}
// Step 2.2 Let n be value current.
let n = length - current;
// Step 2.3 Append n new option elements with no attributes and no child
// nodes to the select element on which this is rooted.
self.add_new_elements(n, can_gc).unwrap();
},
// Step 3. If the given value is less than current, then:
Ordering::Less => {
// Step 3.1. Let n be current value.
// Step 3.2 Remove the last n nodes in the collection from their parent nodes.
for index in (length..current).rev() {
self.Remove(index as i32)
}
},
_ => {},
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-add>
fn Add(
&self,
element: HTMLOptionElementOrHTMLOptGroupElement,
before: Option<HTMLElementOrLong>,
) -> ErrorResult {
let root = self.upcast().root_node();
let node: &Node = match element {
HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => {
element.upcast()
},
HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => {
element.upcast()
},
};
// Step 1
if node.is_ancestor_of(&root) {
return Err(Error::HierarchyRequest);
}
if let Some(HTMLElementOrLong::HTMLElement(ref before_element)) = before {
// Step 2
let before_node = before_element.upcast::<Node>();
if !root.is_ancestor_of(before_node) {
return Err(Error::NotFound);
}
// Step 3
if node == before_node {
return Ok(());
}
}
// Step 4
let reference_node = before.and_then(|before| match before {
HTMLElementOrLong::HTMLElement(element) => Some(DomRoot::upcast::<Node>(element)),
HTMLElementOrLong::Long(index) => self
.upcast()
.IndexedGetter(index as u32)
.map(DomRoot::upcast::<Node>),
});
// Step 5
let parent = if let Some(ref reference_node) = reference_node {
reference_node.GetParentNode().unwrap()
} else {
root
};
// Step 6
Node::pre_insert(node, &parent, reference_node.as_deref(), CanGc::note()).map(|_| ())
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove>
fn Remove(&self, index: i32) {
if let Some(element) = self.upcast().IndexedGetter(index as u32) {
element.Remove(CanGc::note());
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
fn SelectedIndex(&self) -> i32 {
self.upcast()
.root_node()
.downcast::<HTMLSelectElement>()
.expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
.SelectedIndex()
}
/// <https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-selectedindex>
fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) {
self.upcast()
.root_node()
.downcast::<HTMLSelectElement>()
.expect("HTMLOptionsCollection not rooted on a HTMLSelectElement")
.SetSelectedIndex(index, can_gc)
}
}

View file

@ -0,0 +1,200 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::node::{Node, NodeTraits};
use crate::dom::nodelist::NodeList;
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidityState;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLOutputElement {
htmlelement: HTMLElement,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
default_value_override: DomRefCell<Option<DOMString>>,
validity_state: MutNullableDom<ValidityState>,
}
impl HTMLOutputElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLOutputElement {
HTMLOutputElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
form_owner: Default::default(),
labels_node_list: Default::default(),
default_value_override: DomRefCell::new(None),
validity_state: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLOutputElement> {
Node::reflect_node_with_proto(
Box::new(HTMLOutputElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn reset(&self, can_gc: CanGc) {
Node::string_replace_all(self.DefaultValue(), self.upcast::<Node>(), can_gc);
*self.default_value_override.borrow_mut() = None;
}
}
impl HTMLOutputElementMethods<crate::DomTypeHolder> for HTMLOutputElement {
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
// https://html.spec.whatwg.org/multipage/#dom-output-defaultvaleu
fn DefaultValue(&self) -> DOMString {
let dvo = self.default_value_override.borrow();
if let Some(ref dv) = *dvo {
dv.clone()
} else {
self.upcast::<Node>().descendant_text_content()
}
}
// https://html.spec.whatwg.org/multipage/#dom-output-defaultvalue
fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
if self.default_value_override.borrow().is_none() {
// Step 1 ("and return")
Node::string_replace_all(value.clone(), self.upcast::<Node>(), can_gc);
} else {
// Step 2, if not returned from step 1
*self.default_value_override.borrow_mut() = Some(value);
}
}
// https://html.spec.whatwg.org/multipage/#dom-output-value
fn Value(&self) -> DOMString {
self.upcast::<Node>().descendant_text_content()
}
// https://html.spec.whatwg.org/multipage/#dom-output-value
fn SetValue(&self, value: DOMString, can_gc: CanGc) {
*self.default_value_override.borrow_mut() = Some(self.DefaultValue());
Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
}
// https://html.spec.whatwg.org/multipage/#dom-output-type
fn Type(&self) -> DOMString {
DOMString::from("output")
}
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
}
impl VirtualMethods for HTMLOutputElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if attr.local_name() == &local_name!("form") {
self.form_attribute_mutated(mutation, can_gc);
}
}
}
impl FormControl for HTMLOutputElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLOutputElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// output is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
false
}
}

View file

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLParagraphElementBinding::HTMLParagraphElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLParagraphElement {
htmlelement: HTMLElement,
}
impl HTMLParagraphElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLParagraphElement {
HTMLParagraphElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLParagraphElement> {
Node::reflect_node_with_proto(
Box::new(HTMLParagraphElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLParagraphElementMethods<crate::DomTypeHolder> for HTMLParagraphElement {
// https://html.spec.whatwg.org/multipage/#dom-p-align
make_getter!(Align, "align");
// https://html.spec.whatwg.org/multipage/#dom-p-align
make_setter!(SetAlign, "align");
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLParamElement {
htmlelement: HTMLElement,
}
impl HTMLParamElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLParamElement {
HTMLParamElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLParamElement> {
Node::reflect_node_with_proto(
Box::new(HTMLParamElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLPictureElement {
htmlelement: HTMLElement,
}
impl HTMLPictureElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLPictureElement {
HTMLPictureElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLPictureElement> {
Node::reflect_node_with_proto(
Box::new(HTMLPictureElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,75 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use style::attr::AttrValue;
use crate::dom::bindings::codegen::Bindings::HTMLPreElementBinding::HTMLPreElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLPreElement {
htmlelement: HTMLElement,
}
impl HTMLPreElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLPreElement {
HTMLPreElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLPreElement> {
Node::reflect_node_with_proto(
Box::new(HTMLPreElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl VirtualMethods for HTMLPreElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("width") => AttrValue::from_limited_i32(value.into(), 0),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}
impl HTMLPreElementMethods<crate::DomTypeHolder> for HTMLPreElement {
// https://html.spec.whatwg.org/multipage/#dom-pre-width
make_int_getter!(Width, "width", 0);
// https://html.spec.whatwg.org/multipage/#dom-pre-width
make_int_setter!(SetWidth, "width", 0);
}

View file

@ -0,0 +1,234 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Ref;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ElementBinding::Element_Binding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLProgressElementBinding::HTMLProgressElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmldivelement::HTMLDivElement;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, Node, NodeTraits};
use crate::dom::nodelist::NodeList;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLProgressElement {
htmlelement: HTMLElement,
labels_node_list: MutNullableDom<NodeList>,
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
/// Holds handles to all slots in the UA shadow tree
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ShadowTree {
progress_bar: Dom<HTMLDivElement>,
}
impl HTMLProgressElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLProgressElement {
HTMLProgressElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
labels_node_list: MutNullableDom::new(None),
shadow_tree: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLProgressElement> {
Node::reflect_node_with_proto(
Box::new(HTMLProgressElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn create_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let root = self.upcast::<Element>().attach_ua_shadow_root(true, can_gc);
let progress_bar = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
// FIXME: This should use ::-moz-progress-bar
progress_bar
.upcast::<Element>()
.SetId("-servo-progress-bar".into(), can_gc);
root.upcast::<Node>()
.AppendChild(progress_bar.upcast::<Node>(), can_gc)
.unwrap();
let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
progress_bar: progress_bar.as_traced(),
});
self.upcast::<Node>()
.dirty(crate::dom::node::NodeDamage::Other);
}
fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
if !self.upcast::<Element>().is_shadow_host() {
self.create_shadow_tree(can_gc);
}
Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
.ok()
.expect("UA shadow tree was not created")
}
/// Update the visual width of bar
fn update_state(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
let position = (*self.Value() / *self.Max()) * 100.0;
let style = format!("width: {}%", position);
shadow_tree
.progress_bar
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
}
}
impl HTMLProgressElementMethods<crate::DomTypeHolder> for HTMLProgressElement {
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
// https://html.spec.whatwg.org/multipage/#dom-progress-value
fn Value(&self) -> Finite<f64> {
// In case of missing `value`, parse error, or negative `value`, `value` should be
// interpreted as 0. As `get_string_attribute` returns an empty string in case the
// attribute is missing, this case is handeled as the default of the `map_or` function.
//
// It is safe to wrap the number coming from `parse_floating_point_number` as it will
// return Err on inf and nan
self.upcast::<Element>()
.get_string_attribute(&local_name!("value"))
.parse_floating_point_number()
.map_or(Finite::wrap(0.0), |v| {
if v < 0.0 {
Finite::wrap(0.0)
} else {
Finite::wrap(v.min(*self.Max()))
}
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-progress-value>
fn SetValue(&self, new_val: Finite<f64>, can_gc: CanGc) {
if *new_val >= 0.0 {
let mut string_value = DOMString::from_string((*new_val).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>().set_string_attribute(
&local_name!("value"),
string_value,
can_gc,
);
}
}
// https://html.spec.whatwg.org/multipage/#dom-progress-max
fn Max(&self) -> Finite<f64> {
// In case of missing `max`, parse error, or negative `max`, `max` should be interpreted as
// 1.0. As `get_string_attribute` returns an empty string in case the attribute is missing,
// these cases are handeled by `map_or`
self.upcast::<Element>()
.get_string_attribute(&local_name!("max"))
.parse_floating_point_number()
.map_or(Finite::wrap(1.0), |m| {
if m <= 0.0 {
Finite::wrap(1.0)
} else {
Finite::wrap(m)
}
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-progress-max>
fn SetMax(&self, new_val: Finite<f64>, can_gc: CanGc) {
if *new_val > 0.0 {
let mut string_value = DOMString::from_string((*new_val).to_string());
string_value.set_best_representation_of_the_floating_point_number();
self.upcast::<Element>().set_string_attribute(
&local_name!("max"),
string_value,
can_gc,
);
}
}
// https://html.spec.whatwg.org/multipage/#dom-progress-position
fn Position(&self) -> Finite<f64> {
let value = self
.upcast::<Element>()
.get_string_attribute(&local_name!("value"));
if value.is_empty() {
Finite::wrap(-1.0)
} else {
let value = self.Value();
let max = self.Max();
// An unsafe Finite constructor might be nice here, as it's unlikely for the
// compiler to infer the following guarantees. It is probably premature
// optimization though.
//
// Safety: `ret` have to be a finite, defined number. This is the case since both
// value and max is finite, max > 0, and a value >> max cannot exist, as
// Self::Value(&self) enforces value <= max.
Finite::wrap(*value / *max)
}
}
}
impl VirtualMethods for HTMLProgressElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
let is_important_attribute = matches!(
attr.local_name(),
&local_name!("value") | &local_name!("max")
);
if is_important_attribute {
self.update_state(CanGc::note());
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
self.super_type().unwrap().bind_to_tree(context, can_gc);
self.update_state(CanGc::note());
}
}

View file

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLQuoteElementBinding::HTMLQuoteElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::USVString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLQuoteElement {
htmlelement: HTMLElement,
}
impl HTMLQuoteElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLQuoteElement {
HTMLQuoteElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLQuoteElement> {
Node::reflect_node_with_proto(
Box::new(HTMLQuoteElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLQuoteElementMethods<crate::DomTypeHolder> for HTMLQuoteElement {
// https://html.spec.whatwg.org/multipage/#dom-quote-cite
make_url_getter!(Cite, "cite");
// https://html.spec.whatwg.org/multipage/#dom-quote-cite
make_url_setter!(SetCite, "cite");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,824 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::default::Default;
use std::iter;
use dom_struct::dom_struct;
use embedder_traits::{EmbedderMsg, FormControl as EmbedderFormControl};
use embedder_traits::{SelectElementOption, SelectElementOptionOrOptgroup};
use euclid::{Point2D, Rect, Size2D};
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use style::attr::AttrValue;
use stylo_dom::ElementState;
use webrender_api::units::DeviceIntRect;
use base::generic_channel;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::event::{EventBubbles, EventCancelable, EventComposed};
use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::GenericBindings::CharacterDataBinding::CharacterData_Binding::CharacterDataMethods;
use crate::dom::bindings::codegen::UnionTypes::{
HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement,
};
use crate::dom::bindings::error::ErrorResult;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::characterdata::CharacterData;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::html::htmlcollection::CollectionFilter;
use crate::dom::html::htmldivelement::HTMLDivElement;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::html::htmlformelement::{FormControl, FormDatum, FormDatumValue, HTMLFormElement};
use crate::dom::html::htmloptgroupelement::HTMLOptGroupElement;
use crate::dom::html::htmloptionelement::HTMLOptionElement;
use crate::dom::html::htmloptionscollection::HTMLOptionsCollection;
use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeTraits, UnbindContext};
use crate::dom::nodelist::NodeList;
use crate::dom::text::Text;
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
const DEFAULT_SELECT_SIZE: u32 = 0;
const SELECT_BOX_STYLE: &str = "
display: flex;
align-items: center;
height: 100%;
";
const TEXT_CONTAINER_STYLE: &str = "flex: 1;";
const CHEVRON_CONTAINER_STYLE: &str = "
font-size: 16px;
margin: 4px;
";
#[derive(JSTraceable, MallocSizeOf)]
struct OptionsFilter;
impl CollectionFilter for OptionsFilter {
fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool {
if !elem.is::<HTMLOptionElement>() {
return false;
}
let node = elem.upcast::<Node>();
if root.is_parent_of(node) {
return true;
}
match node.GetParentNode() {
Some(optgroup) => optgroup.is::<HTMLOptGroupElement>() && root.is_parent_of(&optgroup),
None => false,
}
}
}
#[dom_struct]
pub(crate) struct HTMLSelectElement {
htmlelement: HTMLElement,
options: MutNullableDom<HTMLOptionsCollection>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
validity_state: MutNullableDom<ValidityState>,
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
/// Holds handles to all elements in the UA shadow tree
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ShadowTree {
selected_option: Dom<Text>,
}
impl HTMLSelectElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLSelectElement {
HTMLSelectElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED | ElementState::VALID,
local_name,
prefix,
document,
),
options: Default::default(),
form_owner: Default::default(),
labels_node_list: Default::default(),
validity_state: Default::default(),
shadow_tree: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLSelectElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLSelectElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
/// <https://html.spec.whatwg.org/multipage/#concept-select-option-list>
pub(crate) fn list_of_options(
&self,
) -> impl Iterator<Item = DomRoot<HTMLOptionElement>> + use<'_> {
self.upcast::<Node>().children().flat_map(|node| {
if node.is::<HTMLOptionElement>() {
let node = DomRoot::downcast::<HTMLOptionElement>(node).unwrap();
Choice3::First(iter::once(node))
} else if node.is::<HTMLOptGroupElement>() {
Choice3::Second(node.children().filter_map(DomRoot::downcast))
} else {
Choice3::Third(iter::empty())
}
})
}
// https://html.spec.whatwg.org/multipage/#placeholder-label-option
fn get_placeholder_label_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
if self.Required() && !self.Multiple() && self.display_size() == 1 {
self.list_of_options().next().filter(|node| {
let parent = node.upcast::<Node>().GetParentNode();
node.Value().is_empty() && parent.as_deref() == Some(self.upcast())
})
} else {
None
}
}
// https://html.spec.whatwg.org/multipage/#the-select-element:concept-form-reset-control
pub(crate) fn reset(&self) {
for opt in self.list_of_options() {
opt.set_selectedness(opt.DefaultSelected());
opt.set_dirtiness(false);
}
self.ask_for_reset();
}
// https://html.spec.whatwg.org/multipage/#ask-for-a-reset
pub(crate) fn ask_for_reset(&self) {
if self.Multiple() {
return;
}
let mut first_enabled: Option<DomRoot<HTMLOptionElement>> = None;
let mut last_selected: Option<DomRoot<HTMLOptionElement>> = None;
for opt in self.list_of_options() {
if opt.Selected() {
opt.set_selectedness(false);
last_selected = Some(DomRoot::from_ref(&opt));
}
let element = opt.upcast::<Element>();
if first_enabled.is_none() && !element.disabled_state() {
first_enabled = Some(DomRoot::from_ref(&opt));
}
}
if let Some(last_selected) = last_selected {
last_selected.set_selectedness(true);
} else if self.display_size() == 1 {
if let Some(first_enabled) = first_enabled {
first_enabled.set_selectedness(true);
}
}
}
pub(crate) fn push_form_data(&self, data_set: &mut Vec<FormDatum>) {
if self.Name().is_empty() {
return;
}
for opt in self.list_of_options() {
let element = opt.upcast::<Element>();
if opt.Selected() && element.enabled_state() {
data_set.push(FormDatum {
ty: self.Type(),
name: self.Name(),
value: FormDatumValue::String(opt.Value()),
});
}
}
}
// https://html.spec.whatwg.org/multipage/#concept-select-pick
pub(crate) fn pick_option(&self, picked: &HTMLOptionElement) {
if !self.Multiple() {
let picked = picked.upcast();
for opt in self.list_of_options() {
if opt.upcast::<HTMLElement>() != picked {
opt.set_selectedness(false);
}
}
}
}
// https://html.spec.whatwg.org/multipage/#concept-select-size
fn display_size(&self) -> u32 {
if self.Size() == 0 {
if self.Multiple() { 4 } else { 1 }
} else {
self.Size()
}
}
fn create_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let root = self.upcast::<Element>().attach_ua_shadow_root(true, can_gc);
let select_box = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
select_box.upcast::<Element>().set_string_attribute(
&local_name!("style"),
SELECT_BOX_STYLE.into(),
can_gc,
);
let text_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
text_container.upcast::<Element>().set_string_attribute(
&local_name!("style"),
TEXT_CONTAINER_STYLE.into(),
can_gc,
);
select_box
.upcast::<Node>()
.AppendChild(text_container.upcast::<Node>(), can_gc)
.unwrap();
let text = Text::new(DOMString::new(), &document, can_gc);
let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
selected_option: text.as_traced(),
});
text_container
.upcast::<Node>()
.AppendChild(text.upcast::<Node>(), can_gc)
.unwrap();
let chevron_container =
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
chevron_container.upcast::<Element>().set_string_attribute(
&local_name!("style"),
CHEVRON_CONTAINER_STYLE.into(),
can_gc,
);
chevron_container
.upcast::<Node>()
.set_text_content_for_element(Some("".into()), can_gc);
select_box
.upcast::<Node>()
.AppendChild(chevron_container.upcast::<Node>(), can_gc)
.unwrap();
root.upcast::<Node>()
.AppendChild(select_box.upcast::<Node>(), can_gc)
.unwrap();
}
fn shadow_tree(&self, can_gc: CanGc) -> Ref<'_, ShadowTree> {
if !self.upcast::<Element>().is_shadow_host() {
self.create_shadow_tree(can_gc);
}
Ref::filter_map(self.shadow_tree.borrow(), Option::as_ref)
.ok()
.expect("UA shadow tree was not created")
}
pub(crate) fn update_shadow_tree(&self, can_gc: CanGc) {
let shadow_tree = self.shadow_tree(can_gc);
let selected_option_text = self
.selected_option()
.or_else(|| self.list_of_options().next())
.map(|option| option.displayed_label())
.unwrap_or_default();
// Replace newlines with whitespace, then collapse and trim whitespace
let displayed_text = itertools::join(selected_option_text.split_whitespace(), " ");
shadow_tree
.selected_option
.upcast::<CharacterData>()
.SetData(displayed_text.trim().into());
}
pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
self.list_of_options()
.find(|opt_elem| opt_elem.Selected())
.or_else(|| self.list_of_options().next())
}
pub(crate) fn show_menu(&self) -> Option<usize> {
let (ipc_sender, ipc_receiver) =
generic_channel::channel().expect("Failed to create IPC channel!");
// Collect list of optgroups and options
let mut index = 0;
let mut embedder_option_from_option = |option: &HTMLOptionElement| {
let embedder_option = SelectElementOption {
id: index,
label: option.displayed_label().into(),
is_disabled: option.Disabled(),
};
index += 1;
embedder_option
};
let options = self
.upcast::<Node>()
.children()
.flat_map(|child| {
if let Some(option) = child.downcast::<HTMLOptionElement>() {
return Some(embedder_option_from_option(option).into());
}
if let Some(optgroup) = child.downcast::<HTMLOptGroupElement>() {
let options = optgroup
.upcast::<Node>()
.children()
.flat_map(DomRoot::downcast::<HTMLOptionElement>)
.map(|option| embedder_option_from_option(&option))
.collect();
let label = optgroup.Label().into();
return Some(SelectElementOptionOrOptgroup::Optgroup { label, options });
}
None
})
.collect();
let rect = self.upcast::<Node>().border_box().unwrap_or_default();
let rect = Rect::new(
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
);
let selected_index = self.list_of_options().position(|option| option.Selected());
let document = self.owner_document();
document.send_to_embedder(EmbedderMsg::ShowFormControl(
document.webview_id(),
DeviceIntRect::from_untyped(&rect.to_box2d()),
EmbedderFormControl::SelectElement(options, selected_index, ipc_sender),
));
let Ok(response) = ipc_receiver.recv() else {
log::error!("Failed to receive response");
return None;
};
response
}
/// <https://html.spec.whatwg.org/multipage/#send-select-update-notifications>
fn send_update_notifications(&self) {
// > When the user agent is to send select update notifications, queue an element task on the
// > user interaction task source given the select element to run these steps:
let this = Trusted::new(self);
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue(task!(send_select_update_notification: move || {
let this = this.root();
// TODO: Step 1. Set the select element's user validity to true.
// Step 2. Fire an event named input at the select element, with the bubbles and composed
// attributes initialized to true.
this.upcast::<EventTarget>()
.fire_event_with_params(
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::Composed,
CanGc::note(),
);
// Step 3. Fire an event named change at the select element, with the bubbles attribute initialized
// to true.
this.upcast::<EventTarget>()
.fire_bubbling_event(atom!("change"), CanGc::note());
}));
}
}
impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement {
/// <https://html.spec.whatwg.org/multipage/#dom-select-add>
fn Add(
&self,
element: HTMLOptionElementOrHTMLOptGroupElement,
before: Option<HTMLElementOrLong>,
) -> ErrorResult {
self.Options().Add(element, before)
}
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_setter!(SetDisabled, "disabled");
/// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#dom-select-multiple
make_bool_getter!(Multiple, "multiple");
// https://html.spec.whatwg.org/multipage/#dom-select-multiple
make_bool_setter!(SetMultiple, "multiple");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-fe-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-select-required
make_bool_getter!(Required, "required");
// https://html.spec.whatwg.org/multipage/#dom-select-required
make_bool_setter!(SetRequired, "required");
// https://html.spec.whatwg.org/multipage/#dom-select-size
make_uint_getter!(Size, "size", DEFAULT_SELECT_SIZE);
// https://html.spec.whatwg.org/multipage/#dom-select-size
make_uint_setter!(SetSize, "size", DEFAULT_SELECT_SIZE);
/// <https://html.spec.whatwg.org/multipage/#dom-select-type>
fn Type(&self) -> DOMString {
DOMString::from(if self.Multiple() {
"select-multiple"
} else {
"select-one"
})
}
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
/// <https://html.spec.whatwg.org/multipage/#dom-select-options>
fn Options(&self) -> DomRoot<HTMLOptionsCollection> {
self.options.or_init(|| {
let window = self.owner_window();
HTMLOptionsCollection::new(&window, self, Box::new(OptionsFilter), CanGc::note())
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-length>
fn Length(&self) -> u32 {
self.Options().Length()
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-length>
fn SetLength(&self, length: u32, can_gc: CanGc) {
self.Options().SetLength(length, can_gc)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-item>
fn Item(&self, index: u32) -> Option<DomRoot<Element>> {
self.Options().upcast().Item(index)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-item>
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
self.Options().IndexedGetter(index)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-setter>
fn IndexedSetter(
&self,
index: u32,
value: Option<&HTMLOptionElement>,
can_gc: CanGc,
) -> ErrorResult {
self.Options().IndexedSetter(index, value, can_gc)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-nameditem>
fn NamedItem(&self, name: DOMString) -> Option<DomRoot<HTMLOptionElement>> {
self.Options()
.NamedGetter(name)
.and_then(DomRoot::downcast::<HTMLOptionElement>)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-remove>
fn Remove_(&self, index: i32) {
self.Options().Remove(index)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-remove>
fn Remove(&self) {
self.upcast::<Element>().Remove(CanGc::note())
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-value>
fn Value(&self) -> DOMString {
self.list_of_options()
.find(|opt_elem| opt_elem.Selected())
.map(|opt_elem| opt_elem.Value())
.unwrap_or_default()
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-value>
fn SetValue(&self, value: DOMString) {
let mut opt_iter = self.list_of_options();
// Reset until we find an <option> with a matching value
for opt in opt_iter.by_ref() {
if opt.Value() == value {
opt.set_selectedness(true);
opt.set_dirtiness(true);
break;
}
opt.set_selectedness(false);
}
// Reset remaining <option> elements
for opt in opt_iter {
opt.set_selectedness(false);
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::VALUE_MISSING, CanGc::note());
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex>
fn SelectedIndex(&self) -> i32 {
self.list_of_options()
.enumerate()
.filter(|(_, opt_elem)| opt_elem.Selected())
.map(|(i, _)| i as i32)
.next()
.unwrap_or(-1)
}
/// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex>
fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) {
let mut selection_did_change = false;
let mut opt_iter = self.list_of_options();
for opt in opt_iter.by_ref().take(index as usize) {
selection_did_change |= opt.Selected();
opt.set_selectedness(false);
}
if let Some(selected_option) = opt_iter.next() {
selection_did_change |= !selected_option.Selected();
selected_option.set_selectedness(true);
selected_option.set_dirtiness(true);
// Reset remaining <option> elements
for opt in opt_iter {
selection_did_change |= opt.Selected();
opt.set_selectedness(false);
}
}
if selection_did_change {
self.update_shadow_tree(can_gc);
}
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
/// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
}
impl VirtualMethods for HTMLSelectElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("required") => {
self.validity_state()
.perform_validation_and_update(ValidationFlags::VALUE_MISSING, can_gc);
},
local_name!("disabled") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(_) => {
el.set_disabled_state(true);
el.set_enabled_state(false);
},
AttributeMutation::Removed => {
el.set_disabled_state(false);
el.set_enabled_state(true);
el.check_ancestors_disabled_state_for_form_control();
},
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::VALUE_MISSING, can_gc);
},
local_name!("form") => {
self.form_attribute_mutated(mutation, can_gc);
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.upcast::<Element>()
.check_ancestors_disabled_state_for_form_control();
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
if node
.ancestors()
.any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
{
el.check_ancestors_disabled_state_for_form_control();
} else {
el.check_disabled_attribute();
}
}
fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(s) = self.super_type() {
s.children_changed(mutation);
}
self.update_shadow_tree(CanGc::note());
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("size") => AttrValue::from_u32(value.into(), DEFAULT_SELECT_SIZE),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}
impl FormControl for HTMLSelectElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLSelectElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
!self.upcast::<Element>().disabled_state() && !is_barred_by_datalist_ancestor(self.upcast())
}
fn perform_validation(
&self,
validate_flags: ValidationFlags,
_can_gc: CanGc,
) -> ValidationFlags {
let mut failed_flags = ValidationFlags::empty();
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
// https://html.spec.whatwg.org/multipage/#the-select-element%3Asuffering-from-being-missing
if validate_flags.contains(ValidationFlags::VALUE_MISSING) && self.Required() {
let placeholder = self.get_placeholder_label_option();
let is_value_missing = !self
.list_of_options()
.any(|e| e.Selected() && placeholder != Some(e));
failed_flags.set(ValidationFlags::VALUE_MISSING, is_value_missing);
}
failed_flags
}
}
impl Activatable for HTMLSelectElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn is_instance_activatable(&self) -> bool {
true
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
let Some(selected_value) = self.show_menu() else {
// The user did not select a value
return;
};
self.SetSelectedIndex(selected_value as i32, can_gc);
self.send_update_notifications();
}
}
enum Choice3<I, J, K> {
First(I),
Second(J),
Third(K),
}
impl<I, J, K, T> Iterator for Choice3<I, J, K>
where
I: Iterator<Item = T>,
J: Iterator<Item = T>,
K: Iterator<Item = T>,
{
type Item = T;
fn next(&mut self) -> Option<T> {
match *self {
Choice3::First(ref mut i) => i.next(),
Choice3::Second(ref mut j) => j.next(),
Choice3::Third(ref mut k) => k.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match *self {
Choice3::First(ref i) => i.size_hint(),
Choice3::Second(ref j) => j.size_hint(),
Choice3::Third(ref k) => k.size_hint(),
}
}
}

View file

@ -0,0 +1,524 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::{Cell, Ref, RefCell};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::gc::RootedVec;
use js::rust::HandleObject;
use script_bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
use crate::ScriptThread;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::{
AssignedNodesOptions, HTMLSlotElementMethods,
};
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
};
use crate::dom::bindings::codegen::UnionTypes::ElementOrText;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::globalscope::GlobalScope;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::mutationobserver::MutationObserver;
use crate::dom::node::{BindContext, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
/// <https://html.spec.whatwg.org/multipage/#the-slot-element>
#[dom_struct]
pub(crate) struct HTMLSlotElement {
htmlelement: HTMLElement,
/// <https://dom.spec.whatwg.org/#slot-assigned-nodes>
assigned_nodes: RefCell<Vec<Slottable>>,
/// <https://html.spec.whatwg.org/multipage/#manually-assigned-nodes>
manually_assigned_nodes: RefCell<Vec<Slottable>>,
/// Whether there is a queued signal change for this element
///
/// Necessary to avoid triggering too many slotchange events
is_in_agents_signal_slots: Cell<bool>,
}
impl HTMLSlotElementMethods<crate::DomTypeHolder> for HTMLSlotElement {
// https://html.spec.whatwg.org/multipage/#dom-slot-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#dom-slot-name
make_atomic_setter!(SetName, "name");
/// <https://html.spec.whatwg.org/multipage/#dom-slot-assignednodes>
fn AssignedNodes(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Node>> {
// Step 1. If options["flatten"] is false, then return this's assigned nodes.
if !options.flatten {
return self
.assigned_nodes
.borrow()
.iter()
.map(|slottable| slottable.node())
.map(DomRoot::from_ref)
.collect();
}
// Step 2. Return the result of finding flattened slottables with this.
rooted_vec!(let mut flattened_slottables);
self.find_flattened_slottables(&mut flattened_slottables);
flattened_slottables
.iter()
.map(|slottable| DomRoot::from_ref(slottable.node()))
.collect()
}
/// <https://html.spec.whatwg.org/multipage/#dom-slot-assignedelements>
fn AssignedElements(&self, options: &AssignedNodesOptions) -> Vec<DomRoot<Element>> {
self.AssignedNodes(options)
.into_iter()
.flat_map(|node| node.downcast::<Element>().map(DomRoot::from_ref))
.collect()
}
/// <https://html.spec.whatwg.org/multipage/#dom-slot-assign>
fn Assign(&self, nodes: Vec<ElementOrText>) {
let cx = GlobalScope::get_cx();
// Step 1. For each node of this's manually assigned nodes, set node's manual slot assignment to null.
for slottable in self.manually_assigned_nodes.borrow().iter() {
slottable.set_manual_slot_assignment(None);
}
// Step 2. Let nodesSet be a new ordered set.
rooted_vec!(let mut nodes_set);
// Step 3. For each node of nodes:
for element_or_text in nodes.into_iter() {
rooted!(in(*cx) let node = match element_or_text {
ElementOrText::Element(element) => Slottable(Dom::from_ref(element.upcast())),
ElementOrText::Text(text) => Slottable(Dom::from_ref(text.upcast())),
});
// Step 3.1 If node's manual slot assignment refers to a slot,
// then remove node from that slot's manually assigned nodes.
if let Some(slot) = node.manual_slot_assignment() {
let mut manually_assigned_nodes = slot.manually_assigned_nodes.borrow_mut();
if let Some(position) = manually_assigned_nodes
.iter()
.position(|value| *value == *node)
{
manually_assigned_nodes.remove(position);
}
}
// Step 3.2 Set node's manual slot assignment to this.
node.set_manual_slot_assignment(Some(self));
// Step 3.3 Append node to nodesSet.
if !nodes_set.contains(&*node) {
nodes_set.push(node.clone());
}
}
// Step 4. Set this's manually assigned nodes to nodesSet.
*self.manually_assigned_nodes.borrow_mut() = nodes_set.iter().cloned().collect();
// Step 5. Run assign slottables for a tree for this's root.
self.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty())
.assign_slottables_for_a_tree();
}
}
/// <https://dom.spec.whatwg.org/#concept-slotable>
///
/// The contained node is assumed to be either `Element` or `Text`
///
/// This field is public to make it easy to construct slottables.
/// As such, it is possible to put Nodes that are not slottables
/// in there. Using a [Slottable] like this will quickly lead to
/// a panic.
#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[repr(transparent)]
pub(crate) struct Slottable(pub Dom<Node>);
/// Data shared between all [slottables](https://dom.spec.whatwg.org/#concept-slotable)
///
/// Note that the [slottable name](https://dom.spec.whatwg.org/#slotable-name) is not
/// part of this. While the spec says that all slottables have a name, only Element's
/// can ever have a non-empty name, so they store it seperately
#[derive(Default, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub struct SlottableData {
/// <https://dom.spec.whatwg.org/#slotable-assigned-slot>
pub(crate) assigned_slot: Option<Dom<HTMLSlotElement>>,
/// <https://dom.spec.whatwg.org/#slottable-manual-slot-assignment>
pub(crate) manual_slot_assignment: Option<Dom<HTMLSlotElement>>,
}
impl HTMLSlotElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLSlotElement {
HTMLSlotElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
assigned_nodes: Default::default(),
manually_assigned_nodes: Default::default(),
is_in_agents_signal_slots: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLSlotElement> {
Node::reflect_node_with_proto(
Box::new(HTMLSlotElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
pub(crate) fn has_assigned_nodes(&self) -> bool {
!self.assigned_nodes.borrow().is_empty()
}
/// <https://dom.spec.whatwg.org/#find-flattened-slotables>
fn find_flattened_slottables(&self, result: &mut RootedVec<Slottable>) {
// Step 1. Let result be an empty list.
debug_assert!(result.is_empty());
// Step 2. If slots root is not a shadow root, then return result.
if self.upcast::<Node>().containing_shadow_root().is_none() {
return;
};
// Step 3. Let slottables be the result of finding slottables given slot.
rooted_vec!(let mut slottables);
self.find_slottables(&mut slottables);
// Step 4. If slottables is the empty list, then append each slottable
// child of slot, in tree order, to slottables.
if slottables.is_empty() {
for child in self.upcast::<Node>().children() {
let is_slottable = matches!(
child.type_id(),
NodeTypeId::Element(_) |
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
);
if is_slottable {
slottables.push(Slottable(child.as_traced()));
}
}
}
// Step 5. For each node in slottables:
for slottable in slottables.iter() {
// Step 5.1 If node is a slot whose root is a shadow root:
match slottable.0.downcast::<HTMLSlotElement>() {
Some(slot_element)
if slot_element
.upcast::<Node>()
.containing_shadow_root()
.is_some() =>
{
// Step 5.1.1 Let temporaryResult be the result of finding flattened slottables given node.
rooted_vec!(let mut temporary_result);
slot_element.find_flattened_slottables(&mut temporary_result);
// Step 5.1.2 Append each slottable in temporaryResult, in order, to result.
result.extend_from_slice(&temporary_result);
},
// Step 5.2 Otherwise, append node to result.
_ => {
result.push(slottable.clone());
},
};
}
// Step 6. Return result.
}
/// <https://dom.spec.whatwg.org/#find-slotables>
///
/// To avoid rooting shenanigans, this writes the returned slottables
/// into the `result` argument
fn find_slottables(&self, result: &mut RootedVec<Slottable>) {
let cx = GlobalScope::get_cx();
// Step 1. Let result be an empty list.
debug_assert!(result.is_empty());
// Step 2. Let root be slots root.
// Step 3. If root is not a shadow root, then return result.
let Some(root) = self.upcast::<Node>().containing_shadow_root() else {
return;
};
// Step 4. Let host be roots host.
let host = root.Host();
// Step 5. If roots slot assignment is "manual":
if root.SlotAssignment() == SlotAssignmentMode::Manual {
// Step 5.1 Let result be « ».
// NOTE: redundant.
// Step 5.2 For each slottable slottable of slots manually assigned nodes,
// if slottables parent is host, append slottable to result.
for slottable in self.manually_assigned_nodes.borrow().iter() {
if slottable
.node()
.GetParentNode()
.is_some_and(|node| &*node == host.upcast::<Node>())
{
result.push(slottable.clone());
}
}
}
// Step 6. Otherwise, for each slottable child slottable of host, in tree order:
else {
for child in host.upcast::<Node>().children() {
let is_slottable = matches!(
child.type_id(),
NodeTypeId::Element(_) |
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
);
if is_slottable {
rooted!(in(*cx) let slottable = Slottable(child.as_traced()));
// Step 6.1 Let foundSlot be the result of finding a slot given slottable.
let found_slot = slottable.find_a_slot(false);
// Step 6.2 If foundSlot is slot, then append slottable to result.
if found_slot.is_some_and(|found_slot| &*found_slot == self) {
result.push(slottable.clone());
}
}
}
}
// Step 7. Return result.
}
/// <https://dom.spec.whatwg.org/#assign-slotables>
pub(crate) fn assign_slottables(&self) {
// Step 1. Let slottables be the result of finding slottables for slot.
rooted_vec!(let mut slottables);
self.find_slottables(&mut slottables);
// Step 2. If slottables and slots assigned nodes are not identical,
// then run signal a slot change for slot.
let slots_are_identical = self.assigned_nodes.borrow().iter().eq(slottables.iter());
if !slots_are_identical {
self.signal_a_slot_change();
}
// NOTE: This is not written in the spec, which is likely a bug (https://github.com/whatwg/dom/issues/1352)
// If we don't disconnect the old slottables from this slot then they'll stay implictly
// connected, which causes problems later on
for slottable in self.assigned_nodes().iter() {
slottable.set_assigned_slot(None);
}
// Step 3. Set slots assigned nodes to slottables.
*self.assigned_nodes.borrow_mut() = slottables.iter().cloned().collect();
// Step 4. For each slottable in slottables, set slottables assigned slot to slot.
for slottable in slottables.iter() {
slottable.set_assigned_slot(Some(self));
}
}
/// <https://dom.spec.whatwg.org/#signal-a-slot-change>
pub(crate) fn signal_a_slot_change(&self) {
self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
if self.is_in_agents_signal_slots.get() {
return;
}
self.is_in_agents_signal_slots.set(true);
// Step 1. Append slot to slots relevant agents signal slots.
ScriptThread::add_signal_slot(self);
// Step 2. Queue a mutation observer microtask.
MutationObserver::queue_mutation_observer_microtask();
}
pub(crate) fn remove_from_signal_slots(&self) {
debug_assert!(self.is_in_agents_signal_slots.get());
self.is_in_agents_signal_slots.set(false);
}
/// Returns the slot's assigned nodes if the root's slot assignment mode
/// is "named", or the manually assigned nodes otherwise
pub(crate) fn assigned_nodes(&self) -> Ref<'_, [Slottable]> {
Ref::map(self.assigned_nodes.borrow(), Vec::as_slice)
}
}
impl Slottable {
/// <https://dom.spec.whatwg.org/#find-a-slot>
pub(crate) fn find_a_slot(&self, open_flag: bool) -> Option<DomRoot<HTMLSlotElement>> {
// Step 1. If slottables parent is null, then return null.
let parent = self.node().GetParentNode()?;
// Step 2. Let shadow be slottables parents shadow root.
// Step 3. If shadow is null, then return null.
let shadow_root = parent
.downcast::<Element>()
.and_then(Element::shadow_root)?;
// Step 4. If the open flag is set and shadows mode is not "open", then return null.
if open_flag && shadow_root.Mode() != ShadowRootMode::Open {
return None;
}
// Step 5. If shadows slot assignment is "manual", then return the slot in shadows descendants whose
// manually assigned nodes contains slottable, if any; otherwise null.
if shadow_root.SlotAssignment() == SlotAssignmentMode::Manual {
for node in shadow_root
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::No)
{
if let Some(slot) = node.downcast::<HTMLSlotElement>() {
if slot.manually_assigned_nodes.borrow().contains(self) {
return Some(DomRoot::from_ref(slot));
}
}
}
return None;
}
// Step 6. Return the first slot in tree order in shadows descendants whose
// name is slottables name, if any; otherwise null.
shadow_root.slot_for_name(&self.name())
}
/// <https://dom.spec.whatwg.org/#assign-a-slot>
pub(crate) fn assign_a_slot(&self) {
// Step 1. Let slot be the result of finding a slot with slottable.
let slot = self.find_a_slot(false);
// Step 2. If slot is non-null, then run assign slottables for slot.
if let Some(slot) = slot {
slot.assign_slottables();
}
}
fn node(&self) -> &Node {
&self.0
}
pub(crate) fn assigned_slot(&self) -> Option<DomRoot<HTMLSlotElement>> {
self.node().assigned_slot()
}
pub(crate) fn set_assigned_slot(&self, assigned_slot: Option<&HTMLSlotElement>) {
self.node().set_assigned_slot(assigned_slot);
}
pub(crate) fn set_manual_slot_assignment(
&self,
manually_assigned_slot: Option<&HTMLSlotElement>,
) {
self.node()
.set_manual_slot_assignment(manually_assigned_slot);
}
pub(crate) fn manual_slot_assignment(&self) -> Option<DomRoot<HTMLSlotElement>> {
self.node().manual_slot_assignment()
}
fn name(&self) -> DOMString {
// NOTE: Only elements have non-empty names
let Some(element) = self.0.downcast::<Element>() else {
return DOMString::new();
};
element.get_string_attribute(&local_name!("slot"))
}
}
impl VirtualMethods for HTMLSlotElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
/// <https://dom.spec.whatwg.org/#shadow-tree-slots>
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if attr.local_name() == &local_name!("name") && attr.namespace() == &ns!() {
if let Some(shadow_root) = self.containing_shadow_root() {
// Shadow roots keep a list of slot descendants, so we need to tell it
// about our name change
let old_value = match mutation {
AttributeMutation::Set(old) => old
.map(|value| value.to_string().into())
.unwrap_or_default(),
AttributeMutation::Removed => attr.value().to_string().into(),
};
shadow_root.unregister_slot(old_value, self);
shadow_root.register_slot(self);
}
// Changing the name might cause slot assignments to change
self.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty())
.assign_slottables_for_a_tree()
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
if !context.tree_is_in_a_shadow_tree {
return;
}
self.containing_shadow_root()
.expect("not in a shadow tree")
.register_slot(self);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.unbind_from_tree(context, can_gc);
}
if let Some(shadow_root) = self.containing_shadow_root() {
shadow_root.unregister_slot(self.Name(), self);
}
}
}
impl js::gc::Rootable for Slottable {}
impl js::gc::Initialize for Slottable {
#[allow(unsafe_code)]
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
unsafe fn initial() -> Option<Self> {
None
}
}

View file

@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot, Root};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::element::AttributeMutation;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlimageelement::HTMLImageElement;
use crate::dom::html::htmlmediaelement::HTMLMediaElement;
use crate::dom::node::{BindContext, Node, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLSourceElement {
htmlelement: HTMLElement,
}
impl HTMLSourceElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLSourceElement {
HTMLSourceElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLSourceElement> {
Node::reflect_node_with_proto(
Box::new(HTMLSourceElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn iterate_next_html_image_element_siblings(
next_siblings_iterator: impl Iterator<Item = Root<Dom<Node>>>,
can_gc: CanGc,
) {
for next_sibling in next_siblings_iterator {
if let Some(html_image_element_sibling) = next_sibling.downcast::<HTMLImageElement>() {
html_image_element_sibling.update_the_image_data(can_gc);
}
}
}
}
impl VirtualMethods for HTMLSourceElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match attr.local_name() {
&local_name!("srcset") |
&local_name!("sizes") |
&local_name!("media") |
&local_name!("type") => {
let next_sibling_iterator = self.upcast::<Node>().following_siblings();
HTMLSourceElement::iterate_next_html_image_element_siblings(
next_sibling_iterator,
CanGc::note(),
);
},
_ => {},
}
}
/// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted>
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
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) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
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,
CanGc::note(),
);
}
}
}
impl HTMLSourceElementMethods<crate::DomTypeHolder> for HTMLSourceElement {
// https://html.spec.whatwg.org/multipage/#dom-source-src
make_url_getter!(Src, "src");
// https://html.spec.whatwg.org/multipage/#dom-source-src
make_url_setter!(SetSrc, "src");
// https://html.spec.whatwg.org/multipage/#dom-source-type
make_getter!(Type, "type");
// https://html.spec.whatwg.org/multipage/#dom-source-type
make_setter!(SetType, "type");
// https://html.spec.whatwg.org/multipage/#dom-source-srcset
make_url_getter!(Srcset, "srcset");
// https://html.spec.whatwg.org/multipage/#dom-source-srcset
make_url_setter!(SetSrcset, "srcset");
// https://html.spec.whatwg.org/multipage/#dom-source-sizes
make_getter!(Sizes, "sizes");
// https://html.spec.whatwg.org/multipage/#dom-source-sizes
make_setter!(SetSizes, "sizes");
// https://html.spec.whatwg.org/multipage/#dom-source-media
make_getter!(Media, "media");
// https://html.spec.whatwg.org/multipage/#dom-source-media
make_setter!(SetMedia, "media");
}

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLSpanElement {
htmlelement: HTMLElement,
}
impl HTMLSpanElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLSpanElement {
HTMLSpanElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLSpanElement> {
Node::reflect_node_with_proto(
Box::new(HTMLSpanElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,446 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use net_traits::ReferrerPolicy;
use script_bindings::root::Dom;
use servo_arc::Arc;
use style::media_queries::MediaList as StyleMediaList;
use style::shared_lock::DeepCloneWithLock;
use style::stylesheets::{AllowImportRules, Origin, Stylesheet, StylesheetContents, UrlExtraData};
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLStyleElementBinding::HTMLStyleElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::csp::{CspReporting, InlineCheckType};
use crate::dom::cssstylesheet::CSSStyleSheet;
use crate::dom::document::Document;
use crate::dom::documentorshadowroot::StylesheetSource;
use crate::dom::element::{AttributeMutation, Element, ElementCreator};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::medialist::MediaList;
use crate::dom::node::{BindContext, ChildrenMutation, Node, NodeTraits, UnbindContext};
use crate::dom::stylesheet::StyleSheet as DOMStyleSheet;
use crate::dom::stylesheetcontentscache::{StylesheetContentsCache, StylesheetContentsCacheKey};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::stylesheet_loader::{StylesheetLoader, StylesheetOwner};
#[dom_struct]
pub(crate) struct HTMLStyleElement {
htmlelement: HTMLElement,
#[conditional_malloc_size_of]
#[no_trace]
stylesheet: DomRefCell<Option<Arc<Stylesheet>>>,
#[no_trace]
stylesheetcontents_cache_key: DomRefCell<Option<StylesheetContentsCacheKey>>,
cssom_stylesheet: MutNullableDom<CSSStyleSheet>,
/// <https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts>
parser_inserted: Cell<bool>,
in_stack_of_open_elements: Cell<bool>,
pending_loads: Cell<u32>,
any_failed_load: Cell<bool>,
}
impl HTMLStyleElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
creator: ElementCreator,
) -> HTMLStyleElement {
HTMLStyleElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
stylesheet: DomRefCell::new(None),
stylesheetcontents_cache_key: DomRefCell::new(None),
cssom_stylesheet: MutNullableDom::new(None),
parser_inserted: Cell::new(creator.is_parser_created()),
in_stack_of_open_elements: Cell::new(creator.is_parser_created()),
pending_loads: Cell::new(0),
any_failed_load: Cell::new(false),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
creator: ElementCreator,
can_gc: CanGc,
) -> DomRoot<HTMLStyleElement> {
Node::reflect_node_with_proto(
Box::new(HTMLStyleElement::new_inherited(
local_name, prefix, document, creator,
)),
document,
proto,
can_gc,
)
}
#[inline]
fn create_media_list(&self, mq_str: &str) -> StyleMediaList {
MediaList::parse_media_list(mq_str, &self.owner_window())
}
pub(crate) fn parse_own_css(&self) {
let node = self.upcast::<Node>();
assert!(node.is_connected());
// Step 4. of <https://html.spec.whatwg.org/multipage/#the-style-element%3Aupdate-a-style-block>
let mut type_attribute = self.Type();
type_attribute.make_ascii_lowercase();
if !type_attribute.is_empty() && type_attribute != "text/css" {
return;
}
let doc = self.owner_document();
let global = &self.owner_global();
// Step 5: If the Should element's inline behavior be blocked by Content Security Policy? algorithm
// returns "Blocked" when executed upon the style element, "style",
// and the style element's child text content, then return. [CSP]
if global
.get_csp_list()
.should_elements_inline_type_behavior_be_blocked(
global,
self.upcast(),
InlineCheckType::Style,
&node.child_text_content(),
)
{
return;
}
let window = node.owner_window();
let data = node
.GetTextContent()
.expect("Element.textContent must be a string");
let shared_lock = node.owner_doc().style_shared_lock().clone();
let mq = Arc::new(shared_lock.wrap(self.create_media_list(&self.Media())));
let loader = StylesheetLoader::for_element(self.upcast());
let stylesheetcontents_create_callback = || {
#[cfg(feature = "tracing")]
let _span = tracing::trace_span!("ParseStylesheet", servo_profiling = true).entered();
StylesheetContents::from_str(
&data,
UrlExtraData(window.get_url().get_arc()),
Origin::Author,
&shared_lock,
Some(&loader),
window.css_error_reporter(),
doc.quirks_mode(),
AllowImportRules::Yes,
/* sanitized_output = */ None,
)
};
// For duplicate style sheets with identical content, `StylesheetContents` can be reused
// to avoid reedundant parsing of the style sheets. Additionally, the cache hit rate of
// stylo's `CascadeDataCache` can now be significantly improved. When shared `StylesheetContents`
// is modified, copy-on-write will occur, see `CSSStyleSheet::will_modify`.
let (cache_key, contents) = StylesheetContentsCache::get_or_insert_with(
&data,
&shared_lock,
UrlExtraData(window.get_url().get_arc()),
doc.quirks_mode(),
stylesheetcontents_create_callback,
);
let sheet = Arc::new(Stylesheet {
contents,
shared_lock,
media: mq,
disabled: AtomicBool::new(false),
});
// No subresource loads were triggered, queue load event
if self.pending_loads.get() == 0 {
self.owner_global()
.task_manager()
.dom_manipulation_task_source()
.queue_simple_event(self.upcast(), atom!("load"));
}
self.set_stylesheet(sheet, cache_key, true);
}
// FIXME(emilio): This is duplicated with HTMLLinkElement::set_stylesheet.
//
// With the reuse of `StylesheetContent` for same stylesheet string content,
// this function has a bit difference with `HTMLLinkElement::set_stylesheet` now.
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn set_stylesheet(
&self,
s: Arc<Stylesheet>,
cache_key: Option<StylesheetContentsCacheKey>,
need_clean_cssom: bool,
) {
let stylesheets_owner = self.stylesheet_list_owner();
if let Some(ref s) = *self.stylesheet.borrow() {
stylesheets_owner
.remove_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s);
}
if need_clean_cssom {
self.clean_stylesheet_ownership();
} else if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
let guard = s.shared_lock.read();
cssom_stylesheet.update_style_stylesheet(&s, &guard);
}
*self.stylesheet.borrow_mut() = Some(s.clone());
*self.stylesheetcontents_cache_key.borrow_mut() = cache_key;
stylesheets_owner.add_owned_stylesheet(self.upcast(), s);
}
pub(crate) fn will_modify_stylesheet(&self) {
if let Some(stylesheet_with_owned_contents) = self.create_owned_contents_stylesheet() {
self.set_stylesheet(stylesheet_with_owned_contents, None, false);
}
}
pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
self.stylesheet.borrow().clone()
}
pub(crate) fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> {
self.get_stylesheet().map(|sheet| {
self.cssom_stylesheet.or_init(|| {
CSSStyleSheet::new(
&self.owner_window(),
Some(self.upcast::<Element>()),
"text/css".into(),
None, // todo handle location
None, // todo handle title
sheet,
None, // constructor_document
CanGc::note(),
)
})
})
}
fn create_owned_contents_stylesheet(&self) -> Option<Arc<Stylesheet>> {
let cache_key = self.stylesheetcontents_cache_key.borrow_mut().take()?;
if cache_key.is_uniquely_owned() {
StylesheetContentsCache::remove(cache_key);
return None;
}
let stylesheet_with_shared_contents = self.stylesheet.borrow().clone()?;
let lock = stylesheet_with_shared_contents.shared_lock.clone();
let guard = stylesheet_with_shared_contents.shared_lock.read();
let stylesheet_with_owned_contents = Arc::new(Stylesheet {
contents: Arc::new(
stylesheet_with_shared_contents
.contents
.deep_clone_with_lock(&lock, &guard),
),
shared_lock: lock,
media: stylesheet_with_shared_contents.media.clone(),
disabled: AtomicBool::new(
stylesheet_with_shared_contents
.disabled
.load(Ordering::SeqCst),
),
});
Some(stylesheet_with_owned_contents)
}
fn clean_stylesheet_ownership(&self) {
if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() {
// If the CSSOMs change from having an owner node to being ownerless, they may still
// potentially modify shared stylesheets. Thus, create an new `Stylesheet` with owned
// `StylesheetContents` to ensure that the potentially modifications are only made on
// the owned `StylesheetContents`.
if let Some(stylesheet) = self.create_owned_contents_stylesheet() {
let guard = stylesheet.shared_lock.read();
cssom_stylesheet.update_style_stylesheet(&stylesheet, &guard);
}
cssom_stylesheet.set_owner_node(None);
}
self.cssom_stylesheet.set(None);
}
fn remove_stylesheet(&self) {
self.clean_stylesheet_ownership();
if let Some(s) = self.stylesheet.borrow_mut().take() {
self.stylesheet_list_owner()
.remove_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), &s);
let _ = self.stylesheetcontents_cache_key.borrow_mut().take();
}
}
}
impl VirtualMethods for HTMLStyleElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn children_changed(&self, mutation: &ChildrenMutation) {
self.super_type().unwrap().children_changed(mutation);
// https://html.spec.whatwg.org/multipage/#update-a-style-block
// Handles the case when:
// "The element is not on the stack of open elements of an HTML parser or XML parser,
// and one of its child nodes is modified by a script."
// TODO: Handle Text child contents being mutated.
let node = self.upcast::<Node>();
if (node.is_in_a_document_tree() || node.is_in_a_shadow_tree()) &&
!self.in_stack_of_open_elements.get()
{
self.parse_own_css();
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
self.super_type().unwrap().bind_to_tree(context, can_gc);
// https://html.spec.whatwg.org/multipage/#update-a-style-block
// Handles the case when:
// "The element is not on the stack of open elements of an HTML parser or XML parser,
// and it becomes connected or disconnected."
if context.tree_connected && !self.in_stack_of_open_elements.get() {
self.parse_own_css();
}
}
fn pop(&self) {
self.super_type().unwrap().pop();
// https://html.spec.whatwg.org/multipage/#update-a-style-block
// Handles the case when:
// "The element is popped off the stack of open elements of an HTML parser or XML parser."
self.in_stack_of_open_elements.set(false);
if self.upcast::<Node>().is_in_a_document_tree() {
self.parse_own_css();
}
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.unbind_from_tree(context, can_gc);
}
if context.tree_connected {
self.remove_stylesheet();
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.attribute_mutated(attr, mutation, can_gc);
}
let node = self.upcast::<Node>();
if !(node.is_in_a_document_tree() || node.is_in_a_shadow_tree()) ||
self.in_stack_of_open_elements.get()
{
return;
}
if attr.name() == "type" {
if let AttributeMutation::Set(Some(old_value)) = mutation {
if **old_value == **attr.value() {
return;
}
}
self.remove_stylesheet();
self.parse_own_css();
} else if attr.name() == "media" {
if let Some(ref stylesheet) = *self.stylesheet.borrow_mut() {
let shared_lock = node.owner_doc().style_shared_lock().clone();
let mut guard = shared_lock.write();
let media = stylesheet.media.write_with(&mut guard);
match mutation {
AttributeMutation::Set(_) => *media = self.create_media_list(&attr.value()),
AttributeMutation::Removed => *media = StyleMediaList::empty(),
};
self.owner_document().invalidate_stylesheets();
}
}
}
}
impl StylesheetOwner for HTMLStyleElement {
fn increment_pending_loads_count(&self) {
self.pending_loads.set(self.pending_loads.get() + 1)
}
fn load_finished(&self, succeeded: bool) -> Option<bool> {
assert!(self.pending_loads.get() > 0, "What finished?");
if !succeeded {
self.any_failed_load.set(true);
}
self.pending_loads.set(self.pending_loads.get() - 1);
if self.pending_loads.get() != 0 {
return None;
}
let any_failed = self.any_failed_load.get();
self.any_failed_load.set(false);
Some(any_failed)
}
fn parser_inserted(&self) -> bool {
self.parser_inserted.get()
}
fn referrer_policy(&self) -> ReferrerPolicy {
ReferrerPolicy::EmptyString
}
fn set_origin_clean(&self, origin_clean: bool) {
if let Some(stylesheet) = self.get_cssom_stylesheet() {
stylesheet.set_origin_clean(origin_clean);
}
}
}
impl HTMLStyleElementMethods<crate::DomTypeHolder> for HTMLStyleElement {
/// <https://drafts.csswg.org/cssom/#dom-linkstyle-sheet>
fn GetSheet(&self) -> Option<DomRoot<DOMStyleSheet>> {
self.get_cssom_stylesheet().map(DomRoot::upcast)
}
/// <https://html.spec.whatwg.org/multipage/#dom-style-disabled>
fn Disabled(&self) -> bool {
self.get_cssom_stylesheet()
.is_some_and(|sheet| sheet.disabled())
}
/// <https://html.spec.whatwg.org/multipage/#dom-style-disabled>
fn SetDisabled(&self, value: bool) {
if let Some(sheet) = self.get_cssom_stylesheet() {
sheet.set_disabled(value);
}
}
// <https://html.spec.whatwg.org/multipage/#HTMLStyleElement-partial>
make_getter!(Type, "type");
// <https://html.spec.whatwg.org/multipage/#HTMLStyleElement-partial>
make_setter!(SetType, "type");
// <https://html.spec.whatwg.org/multipage/#attr-style-media>
make_getter!(Media, "media");
// <https://html.spec.whatwg.org/multipage/#attr-style-media>
make_setter!(SetMedia, "media");
}

View file

@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTableCaptionElement {
htmlelement: HTMLElement,
}
impl HTMLTableCaptionElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableCaptionElement {
HTMLTableCaptionElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableCaptionElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableCaptionElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}

View file

@ -0,0 +1,222 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmltableelement::HTMLTableElement;
use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
use crate::dom::node::{LayoutNodeHelpers, Node, NodeDamage};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
const DEFAULT_COLSPAN: u32 = 1;
const DEFAULT_ROWSPAN: u32 = 1;
#[dom_struct]
pub(crate) struct HTMLTableCellElement {
htmlelement: HTMLElement,
}
impl HTMLTableCellElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableCellElement {
HTMLTableCellElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableCellElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableCellElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}
impl HTMLTableCellElementMethods<crate::DomTypeHolder> for HTMLTableCellElement {
// https://html.spec.whatwg.org/multipage/#dom-tdth-colspan
make_uint_getter!(ColSpan, "colspan", DEFAULT_COLSPAN);
// https://html.spec.whatwg.org/multipage/#dom-tdth-colspan
// > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to
// > the range [1, 1000], and its default value is 1.
make_clamped_uint_setter!(SetColSpan, "colspan", 1, 1000, 1);
// https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan
make_uint_getter!(RowSpan, "rowspan", DEFAULT_ROWSPAN);
// https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan
// > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to
// > the range [0, 65534], and its default value is 1.
make_clamped_uint_setter!(SetRowSpan, "rowspan", 0, 65534, 1);
// https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor
make_getter!(BgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor
make_legacy_color_setter!(SetBgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-tdth-width
make_getter!(Width, "width");
// https://html.spec.whatwg.org/multipage/#dom-tdth-width
make_nonzero_dimension_setter!(SetWidth, "width");
// https://html.spec.whatwg.org/multipage/#dom-tdth-cellindex
fn CellIndex(&self) -> i32 {
let self_node = self.upcast::<Node>();
let parent_children = match self_node.GetParentNode() {
Some(ref parent_node) if parent_node.is::<HTMLTableRowElement>() => {
parent_node.children()
},
_ => return -1,
};
parent_children
.filter(|c| c.is::<HTMLTableCellElement>())
.position(|c| &*c == self_node)
.map_or(-1, |p| p as i32)
}
}
pub(crate) trait HTMLTableCellElementLayoutHelpers<'dom> {
fn get_background_color(self) -> Option<AbsoluteColor>;
fn get_colspan(self) -> Option<u32>;
fn get_rowspan(self) -> Option<u32>;
fn get_table(self) -> Option<LayoutDom<'dom, HTMLTableElement>>;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl<'dom> HTMLTableCellElementLayoutHelpers<'dom> for LayoutDom<'dom, HTMLTableCellElement> {
fn get_background_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_colspan(self) -> Option<u32> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("colspan"))
.map(AttrValue::as_uint)
}
fn get_rowspan(self) -> Option<u32> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("rowspan"))
.map(AttrValue::as_uint)
}
fn get_table(self) -> Option<LayoutDom<'dom, HTMLTableElement>> {
let row = self.upcast::<Node>().composed_parent_node_ref()?;
row.downcast::<HTMLTableRowElement>()?;
let section = row.composed_parent_node_ref()?;
section.downcast::<HTMLTableElement>().or_else(|| {
section.downcast::<HTMLTableSectionElement>()?;
let table = section.composed_parent_node_ref()?;
table.downcast::<HTMLTableElement>()
})
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl VirtualMethods for HTMLTableCellElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
if let Some(super_type) = self.super_type() {
super_type.attribute_mutated(attr, mutation, can_gc);
}
if matches!(*attr.local_name(), local_name!("colspan")) {
self.upcast::<Node>().dirty(NodeDamage::Other);
}
if matches!(*attr.local_name(), local_name!("rowspan")) {
self.upcast::<Node>().dirty(NodeDamage::Other);
}
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("colspan") => {
let mut attr = AttrValue::from_u32(value.into(), DEFAULT_COLSPAN);
if let AttrValue::UInt(_, ref mut value) = attr {
// From <https://html.spec.whatwg.org/multipage/#dom-tdth-colspan>:
// > The colSpan IDL attribute must reflect the colspan content attribute. It is clamped to
// > the range [1, 1000], and its default value is 1.
*value = (*value).clamp(1, 1000);
}
attr
},
local_name!("rowspan") => {
let mut attr = AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN);
if let AttrValue::UInt(_, ref mut value) = attr {
// From <https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan>:
// > The rowSpan IDL attribute must reflect the rowspan content attribute. It is clamped to
// > the range [0, 65534], and its default value is 1.
// Note Firefox floors by 1 in quirks mode, but like Chrome and Safari we don't do that.
*value = (*value).clamp(0, 65534);
}
attr
},
local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
local_name!("height") => AttrValue::from_nonzero_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -0,0 +1,124 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{Node, NodeDamage};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTableColElement {
htmlelement: HTMLElement,
}
impl HTMLTableColElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableColElement {
HTMLTableColElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableColElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableColElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}
impl HTMLTableColElementMethods<crate::DomTypeHolder> for HTMLTableColElement {
// <https://html.spec.whatwg.org/multipage/#attr-col-span>
make_uint_getter!(Span, "span", 1);
// <https://html.spec.whatwg.org/multipage/#attr-col-span>
// > The span IDL attribute must reflect the content attribute of the same name. It is clamped
// > to the range [1, 1000], and its default value is 1.
make_clamped_uint_setter!(SetSpan, "span", 1, 1000, 1);
}
pub(crate) trait HTMLTableColElementLayoutHelpers<'dom> {
fn get_span(self) -> Option<u32>;
fn get_width(self) -> LengthOrPercentageOrAuto;
}
impl<'dom> HTMLTableColElementLayoutHelpers<'dom> for LayoutDom<'dom, HTMLTableColElement> {
fn get_span(self) -> Option<u32> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("span"))
.map(AttrValue::as_uint)
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl VirtualMethods for HTMLTableColElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
if let Some(super_type) = self.super_type() {
super_type.attribute_mutated(attr, mutation, can_gc);
}
if matches!(*attr.local_name(), local_name!("span")) {
self.upcast::<Node>().dirty(NodeDamage::Other);
}
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("span") => {
let mut attr = AttrValue::from_u32(value.into(), 1);
if let AttrValue::UInt(_, ref mut val) = attr {
// From <https://html.spec.whatwg.org/multipage/#attr-col-span>:
// > The span IDL attribute must reflect the content attribute of the same name.
// > It is clamped to the range [1, 1000], and its default value is 1.
*val = (*val).clamp(1, 1000);
}
attr
},
local_name!("width") => AttrValue::from_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -0,0 +1,567 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer};
use style::color::AbsoluteColor;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmltablecaptionelement::HTMLTableCaptionElement;
use crate::dom::html::htmltablecolelement::HTMLTableColElement;
use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTableElement {
htmlelement: HTMLElement,
border: Cell<Option<u32>>,
cellpadding: Cell<Option<u32>>,
cellspacing: Cell<Option<u32>>,
tbodies: MutNullableDom<HTMLCollection>,
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
#[derive(JSTraceable, MallocSizeOf)]
struct TableRowFilter {
sections: Vec<Dom<Node>>,
}
impl CollectionFilter for TableRowFilter {
fn filter(&self, elem: &Element, root: &Node) -> bool {
elem.is::<HTMLTableRowElement>() &&
(root.is_parent_of(elem.upcast()) ||
self.sections
.iter()
.any(|section| section.is_parent_of(elem.upcast())))
}
}
impl HTMLTableElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableElement {
HTMLTableElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
border: Cell::new(None),
cellpadding: Cell::new(None),
cellspacing: Cell::new(None),
tbodies: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
pub(crate) fn get_border(&self) -> Option<u32> {
self.border.get()
}
// https://html.spec.whatwg.org/multipage/#dom-table-thead
// https://html.spec.whatwg.org/multipage/#dom-table-tfoot
fn get_first_section_of_type(
&self,
atom: &LocalName,
) -> Option<DomRoot<HTMLTableSectionElement>> {
self.upcast::<Node>()
.child_elements()
.find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == atom)
.and_then(|n| n.downcast().map(DomRoot::from_ref))
}
// https://html.spec.whatwg.org/multipage/#dom-table-thead
// https://html.spec.whatwg.org/multipage/#dom-table-tfoot
fn set_first_section_of_type<P>(
&self,
atom: &LocalName,
section: Option<&HTMLTableSectionElement>,
reference_predicate: P,
can_gc: CanGc,
) -> ErrorResult
where
P: FnMut(&DomRoot<Element>) -> bool,
{
if let Some(e) = section {
if e.upcast::<Element>().local_name() != atom {
return Err(Error::HierarchyRequest);
}
}
self.delete_first_section_of_type(atom, can_gc);
let node = self.upcast::<Node>();
if let Some(section) = section {
let reference_element = node.child_elements().find(reference_predicate);
let reference_node = reference_element.as_ref().map(|e| e.upcast());
node.InsertBefore(section.upcast(), reference_node, can_gc)?;
}
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-table-createthead
// https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
fn create_section_of_type(
&self,
atom: &LocalName,
can_gc: CanGc,
) -> DomRoot<HTMLTableSectionElement> {
if let Some(section) = self.get_first_section_of_type(atom) {
return section;
}
let section =
HTMLTableSectionElement::new(atom.clone(), None, &self.owner_document(), None, can_gc);
match *atom {
local_name!("thead") => self.SetTHead(Some(&section)),
local_name!("tfoot") => self.SetTFoot(Some(&section)),
_ => unreachable!("unexpected section type"),
}
.expect("unexpected section type");
section
}
// https://html.spec.whatwg.org/multipage/#dom-table-deletethead
// https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
fn delete_first_section_of_type(&self, atom: &LocalName, can_gc: CanGc) {
if let Some(thead) = self.get_first_section_of_type(atom) {
thead.upcast::<Node>().remove_self(can_gc);
}
}
fn get_rows(&self) -> TableRowFilter {
TableRowFilter {
sections: self
.upcast::<Node>()
.children()
.filter_map(|ref node| {
node.downcast::<HTMLTableSectionElement>()
.map(|_| Dom::from_ref(&**node))
})
.collect(),
}
}
}
impl HTMLTableElementMethods<crate::DomTypeHolder> for HTMLTableElement {
// https://html.spec.whatwg.org/multipage/#dom-table-rows
fn Rows(&self) -> DomRoot<HTMLCollection> {
let filter = self.get_rows();
HTMLCollection::new(
&self.owner_window(),
self.upcast(),
Box::new(filter),
CanGc::note(),
)
}
// https://html.spec.whatwg.org/multipage/#dom-table-caption
fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> {
self.upcast::<Node>()
.children()
.filter_map(DomRoot::downcast)
.next()
}
// https://html.spec.whatwg.org/multipage/#dom-table-caption
fn SetCaption(&self, new_caption: Option<&HTMLTableCaptionElement>) -> Fallible<()> {
if let Some(ref caption) = self.GetCaption() {
caption.upcast::<Node>().remove_self(CanGc::note());
}
if let Some(caption) = new_caption {
let node = self.upcast::<Node>();
node.InsertBefore(
caption.upcast(),
node.GetFirstChild().as_deref(),
CanGc::note(),
)?;
}
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-table-createcaption
fn CreateCaption(&self, can_gc: CanGc) -> DomRoot<HTMLTableCaptionElement> {
match self.GetCaption() {
Some(caption) => caption,
None => {
let caption = HTMLTableCaptionElement::new(
local_name!("caption"),
None,
&self.owner_document(),
None,
can_gc,
);
self.SetCaption(Some(&caption))
.expect("Generated caption is invalid");
caption
},
}
}
// https://html.spec.whatwg.org/multipage/#dom-table-deletecaption
fn DeleteCaption(&self) {
if let Some(caption) = self.GetCaption() {
caption.upcast::<Node>().remove_self(CanGc::note());
}
}
// https://html.spec.whatwg.org/multipage/#dom-table-thead
fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
self.get_first_section_of_type(&local_name!("thead"))
}
// https://html.spec.whatwg.org/multipage/#dom-table-thead
fn SetTHead(&self, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
self.set_first_section_of_type(
&local_name!("thead"),
thead,
|n| !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>(),
CanGc::note(),
)
}
// https://html.spec.whatwg.org/multipage/#dom-table-createthead
fn CreateTHead(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
self.create_section_of_type(&local_name!("thead"), can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-table-deletethead
fn DeleteTHead(&self) {
self.delete_first_section_of_type(&local_name!("thead"), CanGc::note())
}
// https://html.spec.whatwg.org/multipage/#dom-table-tfoot
fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
self.get_first_section_of_type(&local_name!("tfoot"))
}
// https://html.spec.whatwg.org/multipage/#dom-table-tfoot
fn SetTFoot(&self, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
self.set_first_section_of_type(
&local_name!("tfoot"),
tfoot,
|n| {
if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
return false;
}
if n.is::<HTMLTableSectionElement>() {
let name = n.local_name();
if name == &local_name!("thead") || name == &local_name!("tbody") {
return false;
}
}
true
},
CanGc::note(),
)
}
// https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
fn CreateTFoot(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
self.create_section_of_type(&local_name!("tfoot"), can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
fn DeleteTFoot(&self) {
self.delete_first_section_of_type(&local_name!("tfoot"), CanGc::note())
}
// https://html.spec.whatwg.org/multipage/#dom-table-tbodies
fn TBodies(&self) -> DomRoot<HTMLCollection> {
self.tbodies.or_init(|| {
HTMLCollection::new_with_filter_fn(
&self.owner_window(),
self.upcast(),
|element, root| {
element.is::<HTMLTableSectionElement>() &&
element.local_name() == &local_name!("tbody") &&
element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
},
CanGc::note(),
)
})
}
// https://html.spec.whatwg.org/multipage/#dom-table-createtbody
fn CreateTBody(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
let tbody = HTMLTableSectionElement::new(
local_name!("tbody"),
None,
&self.owner_document(),
None,
can_gc,
);
let node = self.upcast::<Node>();
let last_tbody = node
.rev_children()
.filter_map(DomRoot::downcast::<Element>)
.find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
node.InsertBefore(tbody.upcast(), reference_element.as_deref(), can_gc)
.expect("Insertion failed");
tbody
}
// https://html.spec.whatwg.org/multipage/#dom-table-insertrow
fn InsertRow(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLTableRowElement>> {
let rows = self.Rows();
let number_of_row_elements = rows.Length();
if index < -1 || index > number_of_row_elements as i32 {
return Err(Error::IndexSize);
}
let new_row = HTMLTableRowElement::new(
local_name!("tr"),
None,
&self.owner_document(),
None,
can_gc,
);
let node = self.upcast::<Node>();
if number_of_row_elements == 0 {
// append new row to last or new tbody in table
if let Some(last_tbody) = node
.rev_children()
.filter_map(DomRoot::downcast::<Element>)
.find(|n| {
n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
})
{
last_tbody
.upcast::<Node>()
.AppendChild(new_row.upcast::<Node>(), can_gc)
.expect("InsertRow failed to append first row.");
} else {
let tbody = self.CreateTBody(can_gc);
node.AppendChild(tbody.upcast(), can_gc)
.expect("InsertRow failed to append new tbody.");
tbody
.upcast::<Node>()
.AppendChild(new_row.upcast::<Node>(), can_gc)
.expect("InsertRow failed to append first row.");
}
} else if index == number_of_row_elements as i32 || index == -1 {
// append new row to parent of last row in table
let last_row = rows
.Item(number_of_row_elements - 1)
.expect("InsertRow failed to find last row in table.");
let last_row_parent = last_row
.upcast::<Node>()
.GetParentNode()
.expect("InsertRow failed to find parent of last row in table.");
last_row_parent
.upcast::<Node>()
.AppendChild(new_row.upcast::<Node>(), can_gc)
.expect("InsertRow failed to append last row.");
} else {
// insert new row before the index-th row in rows using the same parent
let ith_row = rows
.Item(index as u32)
.expect("InsertRow failed to find a row in table.");
let ith_row_parent = ith_row
.upcast::<Node>()
.GetParentNode()
.expect("InsertRow failed to find parent of a row in table.");
ith_row_parent
.upcast::<Node>()
.InsertBefore(
new_row.upcast::<Node>(),
Some(ith_row.upcast::<Node>()),
can_gc,
)
.expect("InsertRow failed to append row");
}
Ok(new_row)
}
/// <https://html.spec.whatwg.org/multipage/#dom-table-deleterow>
fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
let rows = self.Rows();
let num_rows = rows.Length() as i32;
// Step 1: If index is less than 1 or greater than or equal to the number of elements
// in the rows collection, then throw an "IndexSizeError".
if !(-1..num_rows).contains(&index) {
return Err(Error::IndexSize);
}
let num_rows = rows.Length() as i32;
// Step 2: If index is 1, then remove the last element in the rows collection from its
// parent, or do nothing if the rows collection is empty.
if index == -1 {
index = num_rows - 1;
}
if num_rows == 0 {
return Ok(());
}
// Step 3: Otherwise, remove the indexth element in the rows collection from its parent.
DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
Ok(())
}
// https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
make_getter!(BgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
make_legacy_color_setter!(SetBgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-table-width
make_getter!(Width, "width");
// https://html.spec.whatwg.org/multipage/#dom-table-width
make_nonzero_dimension_setter!(SetWidth, "width");
}
pub(crate) trait HTMLTableElementLayoutHelpers {
fn get_background_color(self) -> Option<AbsoluteColor>;
fn get_border(self) -> Option<u32>;
fn get_cellpadding(self) -> Option<u32>;
fn get_cellspacing(self) -> Option<u32>;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> {
fn get_background_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_border(self) -> Option<u32> {
(self.unsafe_get()).border.get()
}
fn get_cellpadding(self) -> Option<u32> {
(self.unsafe_get()).cellpadding.get()
}
fn get_cellspacing(self) -> Option<u32> {
(self.unsafe_get()).cellspacing.get()
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl VirtualMethods for HTMLTableElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("border") => {
// According to HTML5 § 14.3.9, invalid values map to 1px.
self.border.set(
mutation
.new_value(attr)
.map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
);
},
local_name!("cellpadding") => {
self.cellpadding.set(
mutation
.new_value(attr)
.and_then(|value| parse_unsigned_integer(value.chars()).ok()),
);
},
local_name!("cellspacing") => {
self.cellspacing.set(
mutation
.new_value(attr)
.and_then(|value| parse_unsigned_integer(value.chars()).ok()),
);
},
_ => {},
}
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("border") => AttrValue::from_u32(value.into(), 1),
local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
local_name!("height") => AttrValue::from_dimension(value.into()),
local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -0,0 +1,198 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTableRowElementBinding::HTMLTableRowElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::HTMLTableSectionElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::error::{ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::html::htmlcollection::HTMLCollection;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmltablecellelement::HTMLTableCellElement;
use crate::dom::html::htmltableelement::HTMLTableElement;
use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTableRowElement {
htmlelement: HTMLElement,
cells: MutNullableDom<HTMLCollection>,
}
impl HTMLTableRowElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableRowElement {
HTMLTableRowElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
cells: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableRowElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableRowElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
/// Determine the index for this `HTMLTableRowElement` within the given
/// `HTMLCollection`. Returns `-1` if not found within collection.
fn row_index(&self, collection: DomRoot<HTMLCollection>) -> i32 {
collection
.elements_iter()
.position(|elem| (&elem as &Element) == self.upcast())
.map_or(-1, |i| i as i32)
}
}
impl HTMLTableRowElementMethods<crate::DomTypeHolder> for HTMLTableRowElement {
// https://html.spec.whatwg.org/multipage/#dom-tr-bgcolor
make_getter!(BgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-tr-bgcolor
make_legacy_color_setter!(SetBgColor, "bgcolor");
// https://html.spec.whatwg.org/multipage/#dom-tr-cells
fn Cells(&self) -> DomRoot<HTMLCollection> {
self.cells.or_init(|| {
HTMLCollection::new_with_filter_fn(
&self.owner_window(),
self.upcast(),
|element, root| {
(element.is::<HTMLTableCellElement>()) &&
element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
},
CanGc::note(),
)
})
}
// https://html.spec.whatwg.org/multipage/#dom-tr-insertcell
fn InsertCell(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLElement>> {
let node = self.upcast::<Node>();
node.insert_cell_or_row(
index,
|| self.Cells(),
|| HTMLTableCellElement::new(local_name!("td"), None, &node.owner_doc(), None, can_gc),
can_gc,
)
}
// https://html.spec.whatwg.org/multipage/#dom-tr-deletecell
fn DeleteCell(&self, index: i32) -> ErrorResult {
let node = self.upcast::<Node>();
node.delete_cell_or_row(
index,
|| self.Cells(),
|n| n.is::<HTMLTableCellElement>(),
CanGc::note(),
)
}
// https://html.spec.whatwg.org/multipage/#dom-tr-rowindex
fn RowIndex(&self) -> i32 {
let parent = match self.upcast::<Node>().GetParentNode() {
Some(parent) => parent,
None => return -1,
};
if let Some(table) = parent.downcast::<HTMLTableElement>() {
return self.row_index(table.Rows());
}
if !parent.is::<HTMLTableSectionElement>() {
return -1;
}
let grandparent = match parent.upcast::<Node>().GetParentNode() {
Some(parent) => parent,
None => return -1,
};
grandparent
.downcast::<HTMLTableElement>()
.map_or(-1, |table| self.row_index(table.Rows()))
}
// https://html.spec.whatwg.org/multipage/#dom-tr-sectionrowindex
fn SectionRowIndex(&self) -> i32 {
let parent = match self.upcast::<Node>().GetParentNode() {
Some(parent) => parent,
None => return -1,
};
let collection = if let Some(table) = parent.downcast::<HTMLTableElement>() {
table.Rows()
} else if let Some(table_section) = parent.downcast::<HTMLTableSectionElement>() {
table_section.Rows()
} else {
return -1;
};
self.row_index(collection)
}
}
pub(crate) trait HTMLTableRowElementLayoutHelpers {
fn get_background_color(self) -> Option<AbsoluteColor>;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl HTMLTableRowElementLayoutHelpers for LayoutDom<'_, HTMLTableRowElement> {
fn get_background_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl VirtualMethods for HTMLTableRowElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
local_name!("height") => AttrValue::from_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -0,0 +1,138 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use crate::dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::HTMLTableSectionElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::error::{ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::html::htmlcollection::HTMLCollection;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTableSectionElement {
htmlelement: HTMLElement,
}
impl HTMLTableSectionElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTableSectionElement {
HTMLTableSectionElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTableSectionElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTableSectionElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
}
impl HTMLTableSectionElementMethods<crate::DomTypeHolder> for HTMLTableSectionElement {
// https://html.spec.whatwg.org/multipage/#dom-tbody-rows
fn Rows(&self) -> DomRoot<HTMLCollection> {
HTMLCollection::new_with_filter_fn(
&self.owner_window(),
self.upcast(),
|element, root| {
element.is::<HTMLTableRowElement>() &&
element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
},
CanGc::note(),
)
}
// https://html.spec.whatwg.org/multipage/#dom-tbody-insertrow
fn InsertRow(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLElement>> {
let node = self.upcast::<Node>();
node.insert_cell_or_row(
index,
|| self.Rows(),
|| HTMLTableRowElement::new(local_name!("tr"), None, &node.owner_doc(), None, can_gc),
can_gc,
)
}
// https://html.spec.whatwg.org/multipage/#dom-tbody-deleterow
fn DeleteRow(&self, index: i32) -> ErrorResult {
let node = self.upcast::<Node>();
node.delete_cell_or_row(
index,
|| self.Rows(),
|n| n.is::<HTMLTableRowElement>(),
CanGc::note(),
)
}
}
pub(crate) trait HTMLTableSectionElementLayoutHelpers {
fn get_background_color(self) -> Option<AbsoluteColor>;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl HTMLTableSectionElementLayoutHelpers for LayoutDom<'_, HTMLTableSectionElement> {
fn get_background_color(self) -> Option<AbsoluteColor> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
.and_then(AttrValue::as_color)
.cloned()
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
.unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}
impl VirtualMethods for HTMLTableSectionElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
local_name!("height") => AttrValue::from_dimension(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -0,0 +1,155 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{CloneChildrenFlag, Node, NodeTraits};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTemplateElement {
htmlelement: HTMLElement,
/// <https://html.spec.whatwg.org/multipage/#template-contents>
contents: MutNullableDom<DocumentFragment>,
}
impl HTMLTemplateElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTemplateElement {
HTMLTemplateElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
contents: MutNullableDom::new(None),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTemplateElement> {
let n = Node::reflect_node_with_proto(
Box::new(HTMLTemplateElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
);
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
pub(crate) fn set_contents(&self, document_fragment: Option<&DocumentFragment>) {
self.contents.set(document_fragment);
}
}
#[allow(unused_doc_comments)]
impl HTMLTemplateElementMethods<crate::DomTypeHolder> for HTMLTemplateElement {
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootmode>
make_enumerated_getter!(
ShadowRootMode,
"shadowrootmode",
"open" | "closed",
missing => "",
invalid => ""
);
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootmode>
make_atomic_setter!(SetShadowRootMode, "shadowrootmode");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootdelegatesfocus>
make_bool_getter!(ShadowRootDelegatesFocus, "shadowrootdelegatesfocus");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootdelegatesfocus>
make_bool_setter!(SetShadowRootDelegatesFocus, "shadowrootdelegatesfocus");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootclonable>
make_bool_getter!(ShadowRootClonable, "shadowrootclonable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootclonable>
make_bool_setter!(SetShadowRootClonable, "shadowrootclonable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootserializable>
make_bool_getter!(ShadowRootSerializable, "shadowrootserializable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootserializable>
make_bool_setter!(SetShadowRootSerializable, "shadowrootserializable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-content>
fn Content(&self, can_gc: CanGc) -> DomRoot<DocumentFragment> {
self.contents.or_init(|| {
let doc = self.owner_document();
doc.appropriate_template_contents_owner_document(can_gc)
.CreateDocumentFragment(can_gc)
})
}
}
impl VirtualMethods for HTMLTemplateElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
/// <https://html.spec.whatwg.org/multipage/#template-adopting-steps>
fn adopting_steps(&self, old_doc: &Document, can_gc: CanGc) {
self.super_type().unwrap().adopting_steps(old_doc, can_gc);
// Step 1.
let doc = self
.owner_document()
.appropriate_template_contents_owner_document(CanGc::note());
// Step 2.
Node::adopt(self.Content(CanGc::note()).upcast(), &doc, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#the-template-element:concept-node-clone-ext>
fn cloning_steps(
&self,
copy: &Node,
maybe_doc: Option<&Document>,
clone_children: CloneChildrenFlag,
can_gc: CanGc,
) {
self.super_type()
.unwrap()
.cloning_steps(copy, maybe_doc, clone_children, can_gc);
if clone_children == CloneChildrenFlag::DoNotCloneChildren {
// Step 1.
return;
}
let copy = copy.downcast::<HTMLTemplateElement>().unwrap();
// Steps 2-3.
let copy_contents = DomRoot::upcast::<Node>(copy.Content(CanGc::note()));
let copy_contents_doc = copy_contents.owner_doc();
for child in self.Content(CanGc::note()).upcast::<Node>().children() {
let copy_child = Node::clone(
&child,
Some(&copy_contents_doc),
CloneChildrenFlag::CloneChildren,
CanGc::note(),
);
copy_contents.AppendChild(&copy_child, can_gc).unwrap();
}
}
}

View file

@ -0,0 +1,815 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::default::Default;
use std::ops::Range;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::AttrValue;
use stylo_dom::ElementState;
use crate::clipboard_provider::EmbedderClipboardProvider;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::error::ErrorResult;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::clipboardevent::ClipboardEvent;
use crate::dom::compositionevent::CompositionEvent;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
use crate::dom::html::htmlinputelement::HTMLInputElement;
use crate::dom::keyboardevent::KeyboardEvent;
use crate::dom::node::{
BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
};
use crate::dom::nodelist::NodeList;
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::textinput::{
ClipboardEventReaction, Direction, KeyReaction, Lines, SelectionDirection, TextInput,
UTF8Bytes, UTF16CodeUnits,
};
#[dom_struct]
pub(crate) struct HTMLTextAreaElement {
htmlelement: HTMLElement,
#[no_trace]
textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
placeholder: DomRefCell<DOMString>,
// https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
value_dirty: Cell<bool>,
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
validity_state: MutNullableDom<ValidityState>,
}
pub(crate) trait LayoutHTMLTextAreaElementHelpers {
fn value_for_layout(self) -> String;
fn selection_for_layout(self) -> Option<Range<usize>>;
fn get_cols(self) -> u32;
fn get_rows(self) -> u32;
}
#[allow(unsafe_code)]
impl<'dom> LayoutDom<'dom, HTMLTextAreaElement> {
fn textinput_content(self) -> DOMString {
unsafe {
self.unsafe_get()
.textinput
.borrow_for_layout()
.get_content()
}
}
fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
unsafe {
self.unsafe_get()
.textinput
.borrow_for_layout()
.sorted_selection_offsets_range()
}
}
fn placeholder(self) -> &'dom str {
unsafe { self.unsafe_get().placeholder.borrow_for_layout() }
}
}
impl LayoutHTMLTextAreaElementHelpers for LayoutDom<'_, HTMLTextAreaElement> {
fn value_for_layout(self) -> String {
let text = self.textinput_content();
if text.is_empty() {
// FIXME(nox): Would be cool to not allocate a new string if the
// placeholder is single line, but that's an unimportant detail.
self.placeholder().replace("\r\n", "\n").replace('\r', "\n")
} else {
text.into()
}
}
fn selection_for_layout(self) -> Option<Range<usize>> {
if !self.upcast::<Element>().focus_state() {
return None;
}
Some(UTF8Bytes::unwrap_range(
self.textinput_sorted_selection_offsets_range(),
))
}
fn get_cols(self) -> u32 {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("cols"))
.map_or(DEFAULT_COLS, AttrValue::as_uint)
}
fn get_rows(self) -> u32 {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("rows"))
.map_or(DEFAULT_ROWS, AttrValue::as_uint)
}
}
// https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value
const DEFAULT_COLS: u32 = 20;
// https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value
const DEFAULT_ROWS: u32 = 2;
const DEFAULT_MAX_LENGTH: i32 = -1;
const DEFAULT_MIN_LENGTH: i32 = -1;
impl HTMLTextAreaElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTextAreaElement {
let constellation_sender = document
.window()
.as_global_scope()
.script_to_constellation_chan()
.clone();
HTMLTextAreaElement {
htmlelement: HTMLElement::new_inherited_with_state(
ElementState::ENABLED | ElementState::READWRITE,
local_name,
prefix,
document,
),
placeholder: DomRefCell::new(DOMString::new()),
textinput: DomRefCell::new(TextInput::new(
Lines::Multiple,
DOMString::new(),
EmbedderClipboardProvider {
constellation_sender,
webview_id: document.webview_id(),
},
None,
None,
SelectionDirection::None,
)),
value_dirty: Cell::new(false),
form_owner: Default::default(),
labels_node_list: Default::default(),
validity_state: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTextAreaElement> {
Node::reflect_node_with_proto(
Box::new(HTMLTextAreaElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn auto_directionality(&self) -> String {
let value: String = self.Value().to_string();
HTMLInputElement::directionality_from_value(&value)
}
fn update_placeholder_shown_state(&self) {
let has_placeholder = !self.placeholder.borrow().is_empty();
let has_value = !self.textinput.borrow().is_empty();
let el = self.upcast::<Element>();
el.set_placeholder_shown_state(has_placeholder && !has_value);
}
// https://html.spec.whatwg.org/multipage/#concept-fe-mutable
pub(crate) fn is_mutable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
// https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
!(self.upcast::<Element>().disabled_state() || self.ReadOnly())
}
}
impl TextControlElement for HTMLTextAreaElement {
fn selection_api_applies(&self) -> bool {
true
}
fn has_selectable_text(&self) -> bool {
true
}
fn set_dirty_value_flag(&self, value: bool) {
self.value_dirty.set(value)
}
}
impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
// TODO A few of these attributes have default values and additional
// constraints
// https://html.spec.whatwg.org/multipage/#dom-textarea-cols
make_uint_getter!(Cols, "cols", DEFAULT_COLS);
// https://html.spec.whatwg.org/multipage/#dom-textarea-cols
make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS);
// https://html.spec.whatwg.org/multipage/#dom-input-dirName
make_getter!(DirName, "dirname");
// https://html.spec.whatwg.org/multipage/#dom-input-dirName
make_setter!(SetDirName, "dirname");
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_getter!(Disabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
make_bool_setter!(SetDisabled, "disabled");
// https://html.spec.whatwg.org/multipage/#dom-fae-form
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner()
}
// https://html.spec.whatwg.org/multipage/#attr-fe-name
make_getter!(Name, "name");
// https://html.spec.whatwg.org/multipage/#attr-fe-name
make_atomic_setter!(SetName, "name");
// https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
make_getter!(Placeholder, "placeholder");
// https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
make_setter!(SetPlaceholder, "placeholder");
// https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
// https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
// https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
// https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
// https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
make_bool_getter!(ReadOnly, "readonly");
// https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
make_bool_setter!(SetReadOnly, "readonly");
// https://html.spec.whatwg.org/multipage/#dom-textarea-required
make_bool_getter!(Required, "required");
// https://html.spec.whatwg.org/multipage/#dom-textarea-required
make_bool_setter!(SetRequired, "required");
// https://html.spec.whatwg.org/multipage/#dom-textarea-rows
make_uint_getter!(Rows, "rows", DEFAULT_ROWS);
// https://html.spec.whatwg.org/multipage/#dom-textarea-rows
make_limited_uint_setter!(SetRows, "rows", DEFAULT_ROWS);
// https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
make_getter!(Wrap, "wrap");
// https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
make_setter!(SetWrap, "wrap");
// https://html.spec.whatwg.org/multipage/#dom-textarea-type
fn Type(&self) -> DOMString {
DOMString::from("textarea")
}
// https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
fn DefaultValue(&self) -> DOMString {
self.upcast::<Node>().GetTextContent().unwrap()
}
// https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc);
// if the element's dirty value flag is false, then the element's
// raw value must be set to the value of the element's textContent IDL attribute
if !self.value_dirty.get() {
self.reset();
}
}
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
fn Value(&self) -> DOMString {
self.textinput.borrow().get_content()
}
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
fn SetValue(&self, value: DOMString) {
{
let mut textinput = self.textinput.borrow_mut();
// Step 1
let old_value = textinput.get_content();
// Step 2
textinput.set_content(value);
// Step 3
self.value_dirty.set(true);
if old_value != textinput.get_content() {
// Step 4
textinput.clear_selection_to_limit(Direction::Forward);
}
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), CanGc::note());
self.upcast::<Node>().dirty(NodeDamage::Other);
}
// https://html.spec.whatwg.org/multipage/#dom-textarea-textlength
fn TextLength(&self) -> u32 {
let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len();
num_units as u32
}
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
make_labels_getter!(Labels, labels_node_list);
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-select
fn Select(&self) {
self.selection().dom_select();
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
fn GetSelectionStart(&self) -> Option<u32> {
self.selection().dom_start()
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
self.selection().set_dom_start(start)
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
fn GetSelectionEnd(&self) -> Option<u32> {
self.selection().dom_end()
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
self.selection().set_dom_end(end)
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
fn GetSelectionDirection(&self) -> Option<DOMString> {
self.selection().dom_direction()
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
self.selection().set_dom_direction(direction)
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
self.selection().set_dom_range(start, end, direction)
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
self.selection()
.set_dom_range_text(replacement, None, None, Default::default())
}
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
fn SetRangeText_(
&self,
replacement: DOMString,
start: u32,
end: u32,
selection_mode: SelectionMode,
) -> ErrorResult {
self.selection()
.set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
fn WillValidate(&self) -> bool {
self.is_instance_validatable()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
fn Validity(&self) -> DomRoot<ValidityState> {
self.validity_state()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
fn CheckValidity(&self, can_gc: CanGc) -> bool {
self.check_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
fn ReportValidity(&self, can_gc: CanGc) -> bool {
self.report_validity(can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
fn ValidationMessage(&self) -> DOMString {
self.validation_message()
}
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
fn SetCustomValidity(&self, error: DOMString) {
self.validity_state().set_custom_error_message(error);
}
}
impl HTMLTextAreaElement {
/// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
/// Used by WebDriver to clear the textarea element.
pub(crate) fn clear(&self) {
self.value_dirty.set(false);
self.textinput.borrow_mut().set_content(DOMString::from(""));
}
pub(crate) fn reset(&self) {
// https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
let mut textinput = self.textinput.borrow_mut();
textinput.set_content(self.DefaultValue());
self.value_dirty.set(false);
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn selection(&self) -> TextControlSelection<'_, Self> {
TextControlSelection::new(self, &self.textinput)
}
}
impl VirtualMethods for HTMLTextAreaElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("disabled") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(_) => {
el.set_disabled_state(true);
el.set_enabled_state(false);
el.set_read_write_state(false);
},
AttributeMutation::Removed => {
el.set_disabled_state(false);
el.set_enabled_state(true);
el.check_ancestors_disabled_state_for_form_control();
if !el.disabled_state() && !el.read_write_state() {
el.set_read_write_state(true);
}
},
}
el.update_sequentially_focusable_status(CanGc::note());
},
local_name!("maxlength") => match *attr.value() {
AttrValue::Int(_, value) => {
let mut textinput = self.textinput.borrow_mut();
if value < 0 {
textinput.set_max_length(None);
} else {
textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
}
},
_ => panic!("Expected an AttrValue::Int"),
},
local_name!("minlength") => match *attr.value() {
AttrValue::Int(_, value) => {
let mut textinput = self.textinput.borrow_mut();
if value < 0 {
textinput.set_min_length(None);
} else {
textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
}
},
_ => panic!("Expected an AttrValue::Int"),
},
local_name!("placeholder") => {
{
let mut placeholder = self.placeholder.borrow_mut();
placeholder.clear();
if let AttributeMutation::Set(_) = mutation {
placeholder.push_str(&attr.value());
}
}
self.update_placeholder_shown_state();
},
local_name!("readonly") => {
let el = self.upcast::<Element>();
match mutation {
AttributeMutation::Set(_) => {
el.set_read_write_state(false);
},
AttributeMutation::Removed => {
el.set_read_write_state(!el.disabled_state());
},
}
},
local_name!("form") => {
self.form_attribute_mutated(mutation, can_gc);
},
_ => {},
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
self.upcast::<Element>()
.check_ancestors_disabled_state_for_form_control();
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS),
local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS),
local_name!("maxlength") => {
AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
},
local_name!("minlength") => {
AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
},
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
self.super_type().unwrap().unbind_from_tree(context, can_gc);
let node = self.upcast::<Node>();
let el = self.upcast::<Element>();
if node
.ancestors()
.any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
{
el.check_ancestors_disabled_state_for_form_control();
} else {
el.check_disabled_attribute();
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
// The cloning steps for textarea elements must propagate the raw value
// and dirty value flag from the node being cloned to the copy.
fn cloning_steps(
&self,
copy: &Node,
maybe_doc: Option<&Document>,
clone_children: CloneChildrenFlag,
can_gc: CanGc,
) {
if let Some(s) = self.super_type() {
s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
}
let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
el.value_dirty.set(self.value_dirty.get());
{
let mut textinput = el.textinput.borrow_mut();
textinput.set_content(self.textinput.borrow().get_content());
}
el.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(s) = self.super_type() {
s.children_changed(mutation);
}
if !self.value_dirty.get() {
self.reset();
}
}
// copied and modified from htmlinputelement.rs
fn handle_event(&self, event: &Event, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.handle_event(event, can_gc);
}
if event.type_() == atom!("click") && !event.DefaultPrevented() {
// TODO: set the editing position for text inputs
} else if event.type_() == atom!("keydown") && !event.DefaultPrevented() {
if let Some(kevent) = event.downcast::<KeyboardEvent>() {
// This can't be inlined, as holding on to textinput.borrow_mut()
// during self.implicit_submission will cause a panic.
let action = self.textinput.borrow_mut().handle_keydown(kevent);
match action {
KeyReaction::TriggerDefaultAction => (),
KeyReaction::DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::Other);
event.mark_as_handled();
},
KeyReaction::RedrawSelection => {
self.upcast::<Node>().dirty(NodeDamage::Other);
event.mark_as_handled();
},
KeyReaction::Nothing => (),
}
}
} else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")
{
if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
if event.type_() == atom!("compositionend") {
let _ = self
.textinput
.borrow_mut()
.handle_compositionend(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::Other);
} else if event.type_() == atom!("compositionupdate") {
let _ = self
.textinput
.borrow_mut()
.handle_compositionupdate(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::Other);
}
event.mark_as_handled();
}
} else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
let reaction = self
.textinput
.borrow_mut()
.handle_clipboard_event(clipboard_event);
if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
self.owner_document()
.event_handler()
.fire_clipboardchange_event(can_gc);
}
if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
if !reaction.is_empty() {
self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
}
}
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
}
fn pop(&self) {
self.super_type().unwrap().pop();
// https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
self.reset();
}
}
impl FormControl for HTMLTextAreaElement {
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
self.form_owner.get()
}
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
self.form_owner.set(form);
}
fn to_element(&self) -> &Element {
self.upcast::<Element>()
}
}
impl Validatable for HTMLTextAreaElement {
fn as_element(&self) -> &Element {
self.upcast()
}
fn validity_state(&self) -> DomRoot<ValidityState> {
self.validity_state
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
}
fn is_instance_validatable(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
!self.upcast::<Element>().disabled_state() &&
!self.ReadOnly() &&
!is_barred_by_datalist_ancestor(self.upcast())
}
fn perform_validation(
&self,
validate_flags: ValidationFlags,
_can_gc: CanGc,
) -> ValidationFlags {
let mut failed_flags = ValidationFlags::empty();
let textinput = self.textinput.borrow();
let UTF16CodeUnits(value_len) = textinput.utf16_len();
let last_edit_by_user = !textinput.was_last_change_by_set_content();
let value_dirty = self.value_dirty.get();
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
self.Required() &&
self.is_mutable() &&
value_len == 0
{
failed_flags.insert(ValidationFlags::VALUE_MISSING);
}
if value_dirty && last_edit_by_user && value_len > 0 {
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
// https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
if validate_flags.contains(ValidationFlags::TOO_LONG) {
let max_length = self.MaxLength();
if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
failed_flags.insert(ValidationFlags::TOO_LONG);
}
}
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
// https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
if validate_flags.contains(ValidationFlags::TOO_SHORT) {
let min_length = self.MinLength();
if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
failed_flags.insert(ValidationFlags::TOO_SHORT);
}
}
}
failed_flags
}
}

View file

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLTimeElementBinding::HTMLTimeElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTimeElement {
htmlelement: HTMLElement,
}
impl HTMLTimeElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTimeElement {
HTMLTimeElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTimeElement> {
Node::reflect_node_with_proto(
Box::new(HTMLTimeElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
}
impl HTMLTimeElementMethods<crate::DomTypeHolder> for HTMLTimeElement {
// https://html.spec.whatwg.org/multipage/#dom-time-datetime
make_getter!(DateTime, "datetime");
// https://html.spec.whatwg.org/multipage/#dom-time-datetime
make_setter!(SetDateTime, "datetime");
}

View file

@ -0,0 +1,116 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, ChildrenMutation, Node};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLTitleElement {
htmlelement: HTMLElement,
popped: Cell<bool>,
}
impl HTMLTitleElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLTitleElement {
HTMLTitleElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
popped: Cell::new(false),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTitleElement> {
Node::reflect_node_with_proto(
Box::new(HTMLTitleElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
fn notify_title_changed(&self) {
let node = self.upcast::<Node>();
if node.is_in_a_document_tree() {
node.owner_doc().title_changed();
}
}
}
impl HTMLTitleElementMethods<crate::DomTypeHolder> for HTMLTitleElement {
// https://html.spec.whatwg.org/multipage/#dom-title-text
fn Text(&self) -> DOMString {
self.upcast::<Node>().child_text_content()
}
// https://html.spec.whatwg.org/multipage/#dom-title-text
fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
}
}
impl VirtualMethods for HTMLTitleElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn children_changed(&self, mutation: &ChildrenMutation) {
if let Some(s) = self.super_type() {
s.children_changed(mutation);
}
// Notify of title changes only after the initial full parsing
// of the element.
if self.popped.get() {
self.notify_title_changed();
}
}
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context, can_gc);
}
let node = self.upcast::<Node>();
if context.tree_is_in_a_document_tree {
node.owner_doc().title_changed();
}
}
fn pop(&self) {
if let Some(s) = self.super_type() {
s.pop();
}
self.popped.set(true);
// Initial notification of title change, once the full text
// is available.
self.notify_title_changed();
}
}

View file

@ -0,0 +1,143 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding::{
HTMLTrackElementConstants, HTMLTrackElementMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::element::Element;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::texttrack::TextTrack;
use crate::script_runtime::CanGc;
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
#[repr(u16)]
#[allow(unused)]
pub(crate) enum ReadyState {
None = HTMLTrackElementConstants::NONE,
Loading = HTMLTrackElementConstants::LOADING,
Loaded = HTMLTrackElementConstants::LOADED,
Error = HTMLTrackElementConstants::ERROR,
}
#[dom_struct]
pub(crate) struct HTMLTrackElement {
htmlelement: HTMLElement,
ready_state: ReadyState,
track: Dom<TextTrack>,
}
impl HTMLTrackElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
track: &TextTrack,
) -> HTMLTrackElement {
HTMLTrackElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
ready_state: ReadyState::None,
track: Dom::from_ref(track),
}
}
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLTrackElement> {
let track = TextTrack::new(
document.window(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
None,
can_gc,
);
Node::reflect_node_with_proto(
Box::new(HTMLTrackElement::new_inherited(
local_name, prefix, document, &track,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLTrackElementMethods<crate::DomTypeHolder> for HTMLTrackElement {
// https://html.spec.whatwg.org/multipage/#dom-track-kind
fn Kind(&self) -> DOMString {
let element = self.upcast::<Element>();
// Get the value of "kind" and transform all uppercase
// chars into lowercase.
let kind = element
.get_string_attribute(&local_name!("kind"))
.to_lowercase();
match &*kind {
"subtitles" | "captions" | "descriptions" | "chapters" | "metadata" => {
// The value of "kind" is valid. Return the lowercase version
// of it.
DOMString::from(kind)
},
_ if kind.is_empty() => {
// The default value should be "subtitles". If "kind" has not
// been set, the real value for "kind" is "subtitles"
DOMString::from("subtitles")
},
_ => {
// If "kind" has been set but it is not one of the valid
// values, return the default invalid value of "metadata"
DOMString::from("metadata")
},
}
}
// https://html.spec.whatwg.org/multipage/#dom-track-kind
// Do no transformations on the value of "kind" when setting it.
// All transformations should be done in the get method.
make_setter!(SetKind, "kind");
// https://html.spec.whatwg.org/multipage/#dom-track-src
make_url_getter!(Src, "src");
// https://html.spec.whatwg.org/multipage/#dom-track-src
make_url_setter!(SetSrc, "src");
// https://html.spec.whatwg.org/multipage/#dom-track-srclang
make_getter!(Srclang, "srclang");
// https://html.spec.whatwg.org/multipage/#dom-track-srclang
make_setter!(SetSrclang, "srclang");
// https://html.spec.whatwg.org/multipage/#dom-track-label
make_getter!(Label, "label");
// https://html.spec.whatwg.org/multipage/#dom-track-label
make_setter!(SetLabel, "label");
// https://html.spec.whatwg.org/multipage/#dom-track-default
make_bool_getter!(Default, "default");
// https://html.spec.whatwg.org/multipage/#dom-track-default
make_bool_setter!(SetDefault, "default");
// https://html.spec.whatwg.org/multipage/#dom-track-readystate
fn ReadyState(&self) -> u16 {
self.ready_state as u16
}
// https://html.spec.whatwg.org/multipage/#dom-track-track
fn Track(&self) -> DomRoot<TextTrack> {
DomRoot::from_ref(&*self.track)
}
}

View file

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLUListElementBinding::HTMLUListElementMethods;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLUListElement {
htmlelement: HTMLElement,
}
impl HTMLUListElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLUListElement {
HTMLUListElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLUListElement> {
Node::reflect_node_with_proto(
Box::new(HTMLUListElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}
impl HTMLUListElementMethods<crate::DomTypeHolder> for HTMLUListElement {
// https://html.spec.whatwg.org/multipage/#dom-ul-compact
make_bool_getter!(Compact, "compact");
// https://html.spec.whatwg.org/multipage/#dom-ul-compact
make_bool_setter!(SetCompact, "compact");
// https://html.spec.whatwg.org/multipage/#dom-ul-type
make_getter!(Type, "type");
// https://html.spec.whatwg.org/multipage/#dom-ul-type
make_setter!(SetType, "type");
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use js::rust::HandleObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLUnknownElement {
htmlelement: HTMLElement,
}
impl HTMLUnknownElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLUnknownElement {
HTMLUnknownElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLUnknownElement> {
Node::reflect_node_with_proto(
Box::new(HTMLUnknownElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
}

View file

@ -0,0 +1,572 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::sync::Arc;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use layout_api::{HTMLMediaData, MediaMetadata};
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
PendingImageId, UsePlaceholder,
};
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
use net_traits::{
CoreResourceThread, FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError,
ResourceFetchTiming, ResourceTimingType,
};
use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
use servo_media::player::video::VideoFrame;
use servo_url::ServoUrl;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use crate::document_loader::{LoadBlocker, LoadType};
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HTMLVideoElementBinding::HTMLVideoElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::csp::{GlobalCspReporting, Violation};
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::globalscope::GlobalScope;
use crate::dom::html::htmlmediaelement::{HTMLMediaElement, NetworkState, ReadyState};
use crate::dom::node::{Node, NodeTraits};
use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::virtualmethods::VirtualMethods;
use crate::fetch::FetchCanceller;
use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct HTMLVideoElement {
htmlmediaelement: HTMLMediaElement,
/// <https://html.spec.whatwg.org/multipage/#dom-video-videowidth>
video_width: Cell<Option<u32>>,
/// <https://html.spec.whatwg.org/multipage/#dom-video-videoheight>
video_height: Cell<Option<u32>>,
/// Incremented whenever tasks associated with this element are cancelled.
generation_id: Cell<u32>,
/// Load event blocker. Will block the load event while the poster frame
/// is being fetched.
load_blocker: DomRefCell<Option<LoadBlocker>>,
/// A copy of the last frame
#[ignore_malloc_size_of = "VideoFrame"]
#[no_trace]
last_frame: DomRefCell<Option<VideoFrame>>,
/// Indicates if it has already sent a resize event for a given size
sent_resize: Cell<Option<(u32, u32)>>,
}
impl HTMLVideoElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLVideoElement {
HTMLVideoElement {
htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document),
video_width: Cell::new(None),
video_height: Cell::new(None),
generation_id: Cell::new(0),
load_blocker: Default::default(),
last_frame: Default::default(),
sent_resize: Cell::new(None),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLVideoElement> {
Node::reflect_node_with_proto(
Box::new(HTMLVideoElement::new_inherited(
local_name, prefix, document,
)),
document,
proto,
can_gc,
)
}
pub(crate) fn get_video_width(&self) -> Option<u32> {
self.video_width.get()
}
pub(crate) fn get_video_height(&self) -> Option<u32> {
self.video_height.get()
}
/// <https://html.spec.whatwg.org/multipage#event-media-resize>
pub(crate) fn resize(&self, width: Option<u32>, height: Option<u32>) -> Option<(u32, u32)> {
self.video_width.set(width);
self.video_height.set(height);
let width = width?;
let height = height?;
if self.sent_resize.get() == Some((width, height)) {
return None;
}
let sent_resize = if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
None
} else {
self.owner_global()
.task_manager()
.media_element_task_source()
.queue_simple_event(self.upcast(), atom!("resize"));
Some((width, height))
};
self.sent_resize.set(sent_resize);
sent_resize
}
/// Gets the copy of the video frame at the current playback position,
/// if that is available, or else (e.g. when the video is seeking or buffering)
/// its previous appearance, if any.
pub(crate) fn get_current_frame_data(&self) -> Option<Snapshot> {
let frame = self.htmlmediaelement.get_current_frame();
if frame.is_some() {
*self.last_frame.borrow_mut() = frame;
}
match self.last_frame.borrow().as_ref() {
Some(frame) => {
let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
if !frame.is_gl_texture() {
let alpha_mode = SnapshotAlphaMode::Transparent {
premultiplied: false,
};
Some(Snapshot::from_vec(
size.cast(),
SnapshotPixelFormat::BGRA,
alpha_mode,
frame.get_data().to_vec(),
))
} else {
// XXX(victor): here we only have the GL texture ID.
Some(Snapshot::cleared(size.cast()))
}
},
None => None,
}
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
fn update_poster_frame(&self, poster_url: Option<&str>, can_gc: CanGc) {
// Step 1. If there is an existing instance of this algorithm running
// for this video element, abort that instance of this algorithm without
// changing the poster frame.
self.generation_id.set(self.generation_id.get() + 1);
// Step 2. If the poster attribute's value is the empty string or
// if the attribute is absent, then there is no poster frame; return.
let Some(poster_url) = poster_url.filter(|poster_url| !poster_url.is_empty()) else {
self.htmlmediaelement.set_poster_frame(None);
return;
};
// Step 3. Let url be the result of encoding-parsing a URL given
// the poster attribute's value, relative to the element's node
// document.
// Step 4. If url is failure, then return. There is no poster frame.
let poster_url = match self.owner_document().encoding_parse_a_url(poster_url) {
Ok(url) => url,
Err(_) => {
self.htmlmediaelement.set_poster_frame(None);
return;
},
};
// We use the image cache for poster frames so we save as much
// network activity as possible.
let window = self.owner_window();
let image_cache = window.image_cache();
let cache_result = image_cache.get_cached_image_status(
poster_url.clone(),
window.origin().immutable().clone(),
None,
UsePlaceholder::No,
);
let id = match cache_result {
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
image,
url,
..
}) => {
self.process_image_response(ImageResponse::Loaded(image, url), can_gc);
return;
},
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(_, id)) => id,
ImageCacheResult::ReadyForRequest(id) => {
self.do_fetch_poster_frame(poster_url, id, can_gc);
id
},
ImageCacheResult::LoadError => {
self.process_image_response(ImageResponse::None, can_gc);
return;
},
ImageCacheResult::Pending(id) => id,
};
let trusted_node = Trusted::new(self);
let generation = self.generation_id();
let sender = window.register_image_cache_listener(id, move |response| {
let element = trusted_node.root();
// Ignore any image response for a previous request that has been discarded.
if generation != element.generation_id() {
return;
}
element.process_image_response(response.response, CanGc::note());
});
image_cache.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
fn do_fetch_poster_frame(&self, poster_url: ServoUrl, id: PendingImageId, can_gc: CanGc) {
// Step 5. Let request be a new request whose URL is url, client is the element's node
// document's relevant settings object, destination is "image", initiator type is "video",
// credentials mode is "include", and whose use-URL-credentials flag is set.
let document = self.owner_document();
let request = RequestBuilder::new(
Some(document.webview_id()),
poster_url.clone(),
document.global().get_referrer(),
)
.destination(Destination::Image)
.credentials_mode(CredentialsMode::Include)
.use_url_credentials(true)
.origin(document.origin().immutable().clone())
.pipeline_id(Some(document.global().pipeline_id()))
.insecure_requests_policy(document.insecure_requests_policy())
.has_trustworthy_ancestor_origin(document.has_trustworthy_ancestor_origin())
.policy_container(document.policy_container().to_owned());
// Step 6. Fetch request. This must delay the load event of the element's node document.
// This delay must be independent from the ones created by HTMLMediaElement during
// its media load algorithm, otherwise a code like
// <video poster="poster.png"></video>
// (which triggers no media load algorithm unless a explicit call to .load() is done)
// will block the document's load event forever.
let blocker = &self.load_blocker;
LoadBlocker::terminate(blocker, can_gc);
*blocker.borrow_mut() = Some(LoadBlocker::new(
&self.owner_document(),
LoadType::Image(poster_url.clone()),
));
let context = PosterFrameFetchContext::new(
self,
poster_url,
id,
request.id,
self.global().core_resource_thread(),
);
self.owner_document().fetch_background(request, context);
}
fn generation_id(&self) -> u32 {
self.generation_id.get()
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
fn process_image_response(&self, response: ImageResponse, can_gc: CanGc) {
// Step 7. If an image is thus obtained, the poster frame is that image.
// Otherwise, there is no poster frame.
match response {
ImageResponse::Loaded(image, url) => {
debug!("Loaded poster image for video element: {:?}", url);
match image.as_raster_image() {
Some(image) => self.htmlmediaelement.set_poster_frame(Some(image)),
None => warn!("Vector images are not yet supported in video poster"),
}
LoadBlocker::terminate(&self.load_blocker, can_gc);
},
ImageResponse::MetadataLoaded(..) => {},
// The image cache may have loaded a placeholder for an invalid poster url
ImageResponse::PlaceholderLoaded(..) | ImageResponse::None => {
self.htmlmediaelement.set_poster_frame(None);
// A failed load should unblock the document load.
LoadBlocker::terminate(&self.load_blocker, can_gc);
},
}
}
/// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
pub(crate) fn is_usable(&self) -> bool {
!matches!(
self.htmlmediaelement.get_ready_state(),
ReadyState::HaveNothing | ReadyState::HaveMetadata
)
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.htmlmediaelement.origin_is_clean()
}
pub(crate) fn is_network_state_empty(&self) -> bool {
self.htmlmediaelement.network_state() == NetworkState::Empty
}
}
impl HTMLVideoElementMethods<crate::DomTypeHolder> for HTMLVideoElement {
// https://html.spec.whatwg.org/multipage/#dom-video-videowidth
fn VideoWidth(&self) -> u32 {
if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
return 0;
}
self.video_width.get().unwrap_or(0)
}
// https://html.spec.whatwg.org/multipage/#dom-video-videoheight
fn VideoHeight(&self) -> u32 {
if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
return 0;
}
self.video_height.get().unwrap_or(0)
}
// https://html.spec.whatwg.org/multipage/#dom-video-poster
make_getter!(Poster, "poster");
// https://html.spec.whatwg.org/multipage/#dom-video-poster
make_setter!(SetPoster, "poster");
// For testing purposes only. This is not an event from
// https://html.spec.whatwg.org/multipage/#dom-video-poster
event_handler!(postershown, GetOnpostershown, SetOnpostershown);
}
impl VirtualMethods for HTMLVideoElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLMediaElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
if attr.local_name() == &local_name!("poster") {
if let Some(new_value) = mutation.new_value(attr) {
self.update_poster_frame(Some(&new_value), CanGc::note())
} else {
self.update_poster_frame(None, CanGc::note())
}
};
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("width") | &local_name!("height") => {
AttrValue::from_dimension(value.into())
},
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
}
struct PosterFrameFetchContext {
/// Reference to the script thread image cache.
image_cache: Arc<dyn ImageCache>,
/// The element that initiated the request.
elem: Trusted<HTMLVideoElement>,
/// The cache ID for this request.
id: PendingImageId,
/// True if this response is invalid and should be ignored.
cancelled: bool,
/// Timing data for this resource
resource_timing: ResourceFetchTiming,
/// Url for the resource
url: ServoUrl,
/// A [`FetchCanceller`] for this request.
fetch_canceller: FetchCanceller,
}
impl FetchResponseListener for PosterFrameFetchContext {
fn process_request_body(&mut self, _: RequestId) {}
fn process_request_eof(&mut self, _: RequestId) {
self.fetch_canceller.ignore()
}
fn process_response(
&mut self,
request_id: RequestId,
metadata: Result<FetchMetadata, NetworkError>,
) {
self.image_cache.notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
);
let metadata = metadata.ok().map(|meta| match meta {
FetchMetadata::Unfiltered(m) => m,
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
});
let status_is_ok = metadata
.as_ref()
.map_or(true, |m| m.status.in_range(200..300));
if !status_is_ok {
self.cancelled = true;
self.fetch_canceller.cancel();
}
}
fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
if self.cancelled {
// An error was received previously, skip processing the payload.
return;
}
self.image_cache.notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponseChunk(request_id, payload),
);
}
fn process_response_eof(
&mut self,
request_id: RequestId,
response: Result<ResourceFetchTiming, NetworkError>,
) {
self.image_cache.notify_pending_response(
self.id,
FetchResponseMsg::ProcessResponseEOF(request_id, response),
);
}
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
&mut self.resource_timing
}
fn resource_timing(&self) -> &ResourceFetchTiming {
&self.resource_timing
}
fn submit_resource_timing(&mut self) {
network_listener::submit_timing(self, CanGc::note())
}
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations, None, None);
}
}
impl ResourceTimingListener for PosterFrameFetchContext {
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
let initiator_type = InitiatorType::LocalName(
self.elem
.root()
.upcast::<Element>()
.local_name()
.to_string(),
);
(initiator_type, self.url.clone())
}
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
self.elem.root().owner_document().global()
}
}
impl PreInvoke for PosterFrameFetchContext {
fn should_invoke(&self) -> bool {
true
}
}
impl PosterFrameFetchContext {
fn new(
elem: &HTMLVideoElement,
url: ServoUrl,
id: PendingImageId,
request_id: RequestId,
core_resource_thread: CoreResourceThread,
) -> PosterFrameFetchContext {
let window = elem.owner_window();
PosterFrameFetchContext {
image_cache: window.image_cache(),
elem: Trusted::new(elem),
id,
cancelled: false,
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
url,
fetch_canceller: FetchCanceller::new(request_id, core_resource_thread),
}
}
}
pub(crate) trait LayoutHTMLVideoElementHelpers {
fn data(self) -> HTMLMediaData;
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
impl LayoutDom<'_, HTMLVideoElement> {
fn width_attr(self) -> Option<LengthOrPercentageOrAuto> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("width"))
.map(AttrValue::as_dimension)
.cloned()
}
fn height_attr(self) -> Option<LengthOrPercentageOrAuto> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("height"))
.map(AttrValue::as_dimension)
.cloned()
}
}
impl LayoutHTMLVideoElementHelpers for LayoutDom<'_, HTMLVideoElement> {
fn data(self) -> HTMLMediaData {
let video = self.unsafe_get();
// Get the current frame being rendered.
let current_frame = video.htmlmediaelement.get_current_frame_to_present();
// This value represents the natural width and height of the video.
// It may exist even if there is no current frame (for example, after the
// metadata of the video is loaded).
let metadata = video
.get_video_width()
.zip(video.get_video_height())
.map(|(width, height)| MediaMetadata { width, height });
HTMLMediaData {
current_frame,
metadata,
}
}
fn get_width(self) -> LengthOrPercentageOrAuto {
self.width_attr().unwrap_or(LengthOrPercentageOrAuto::Auto)
}
fn get_height(self) -> LengthOrPercentageOrAuto {
self.height_attr().unwrap_or(LengthOrPercentageOrAuto::Auto)
}
}

View file

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub(crate) mod htmlanchorelement;
pub(crate) mod htmlareaelement;
pub(crate) mod htmlaudioelement;
pub(crate) mod htmlbaseelement;
pub(crate) mod htmlbodyelement;
pub(crate) mod htmlbrelement;
pub(crate) mod htmlbuttonelement;
#[allow(dead_code)]
pub(crate) mod htmlcanvaselement;
pub(crate) mod htmlcollection;
pub(crate) mod htmldataelement;
pub(crate) mod htmldatalistelement;
pub(crate) mod htmldetailselement;
pub(crate) mod htmldialogelement;
pub(crate) mod htmldirectoryelement;
pub(crate) mod htmldivelement;
pub(crate) mod htmldlistelement;
pub(crate) mod htmldocument;
pub(crate) mod htmlelement;
pub(crate) mod htmlembedelement;
pub(crate) mod htmlfieldsetelement;
pub(crate) mod htmlfontelement;
pub(crate) mod htmlformcontrolscollection;
pub(crate) mod htmlformelement;
pub(crate) mod htmlframeelement;
pub(crate) mod htmlframesetelement;
pub(crate) mod htmlheadelement;
pub(crate) mod htmlheadingelement;
pub(crate) mod htmlhrelement;
pub(crate) mod htmlhtmlelement;
pub(crate) mod htmlhyperlinkelementutils;
pub(crate) mod htmliframeelement;
pub(crate) mod htmlimageelement;
pub(crate) mod htmlinputelement;
pub(crate) mod htmllabelelement;
pub(crate) mod htmllegendelement;
pub(crate) mod htmllielement;
pub(crate) mod htmllinkelement;
pub(crate) mod htmlmapelement;
pub(crate) mod htmlmediaelement;
pub(crate) mod htmlmenuelement;
pub(crate) mod htmlmetaelement;
pub(crate) mod htmlmeterelement;
pub(crate) mod htmlmodelement;
pub(crate) mod htmlobjectelement;
pub(crate) mod htmlolistelement;
pub(crate) mod htmloptgroupelement;
pub(crate) mod htmloptionelement;
pub(crate) mod htmloptionscollection;
pub(crate) mod htmloutputelement;
pub(crate) mod htmlparagraphelement;
pub(crate) mod htmlparamelement;
pub(crate) mod htmlpictureelement;
pub(crate) mod htmlpreelement;
pub(crate) mod htmlprogresselement;
pub(crate) mod htmlquoteelement;
#[allow(dead_code)]
pub(crate) mod htmlscriptelement;
pub(crate) mod htmlselectelement;
pub(crate) mod htmlslotelement;
pub(crate) mod htmlsourceelement;
pub(crate) mod htmlspanelement;
pub(crate) mod htmlstyleelement;
pub(crate) mod htmltablecaptionelement;
pub(crate) mod htmltablecellelement;
pub(crate) mod htmltablecolelement;
pub(crate) mod htmltableelement;
pub(crate) mod htmltablerowelement;
pub(crate) mod htmltablesectionelement;
pub(crate) mod htmltemplateelement;
pub(crate) mod htmltextareaelement;
pub(crate) mod htmltimeelement;
pub(crate) mod htmltitleelement;
pub(crate) mod htmltrackelement;
pub(crate) mod htmlulistelement;
pub(crate) mod htmlunknownelement;
pub(crate) mod htmlvideoelement;