mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
This moves Servo closer to the focus parts of the HTML specification. The behavior should be the same as before, but now the code in `script` matches the structure of the specification. The main goal is to set us up for: - Firing focus events in the right order on nested documents - A proper implementation of the unfocusing steps. Testing: This should not change behavior so is covered by existing tests. Signed-off-by: Martin Robinson <mrobinson@fastmail.fm> Co-authored-by: Martin Robinson <mrobinson@fastmail.fm>
207 lines
7.2 KiB
Rust
207 lines
7.2 KiB
Rust
/* 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 script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
|
|
use script_bindings::codegen::GenericBindings::WindowBinding::ScrollBehavior;
|
|
use script_bindings::str::DOMString;
|
|
use stylo_dom::ElementState;
|
|
|
|
use crate::dom::attr::Attr;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLOrSVGElementBinding::FocusOptions;
|
|
use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods;
|
|
use crate::dom::bindings::inheritance::Castable;
|
|
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
|
use crate::dom::css::cssstyledeclaration::{
|
|
CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner,
|
|
};
|
|
use crate::dom::document::Document;
|
|
use crate::dom::document::focus::FocusableArea;
|
|
use crate::dom::element::{AttributeMutation, Element};
|
|
use crate::dom::node::{Node, NodeTraits};
|
|
use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement};
|
|
use crate::dom::virtualmethods::VirtualMethods;
|
|
use crate::script_runtime::CanGc;
|
|
|
|
#[dom_struct]
|
|
pub(crate) struct SVGElement {
|
|
element: Element,
|
|
style_decl: MutNullableDom<CSSStyleDeclaration>,
|
|
}
|
|
|
|
impl SVGElement {
|
|
fn new_inherited(
|
|
tag_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
) -> SVGElement {
|
|
SVGElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document)
|
|
}
|
|
|
|
pub(crate) fn new_inherited_with_state(
|
|
state: ElementState,
|
|
tag_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
) -> SVGElement {
|
|
SVGElement {
|
|
element: Element::new_inherited_with_state(state, tag_name, ns!(svg), prefix, document),
|
|
style_decl: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new(
|
|
cx: &mut js::context::JSContext,
|
|
tag_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
proto: Option<HandleObject>,
|
|
) -> DomRoot<SVGElement> {
|
|
Node::reflect_node_with_proto(
|
|
cx,
|
|
Box::new(SVGElement::new_inherited(tag_name, prefix, document)),
|
|
document,
|
|
proto,
|
|
)
|
|
}
|
|
|
|
fn as_element(&self) -> &Element {
|
|
self.upcast::<Element>()
|
|
}
|
|
}
|
|
|
|
impl VirtualMethods for SVGElement {
|
|
fn super_type(&self) -> Option<&dyn VirtualMethods> {
|
|
Some(self.as_element() as &dyn VirtualMethods)
|
|
}
|
|
|
|
fn attribute_mutated(
|
|
&self,
|
|
cx: &mut js::context::JSContext,
|
|
attr: &Attr,
|
|
mutation: AttributeMutation,
|
|
) {
|
|
self.super_type()
|
|
.unwrap()
|
|
.attribute_mutated(cx, attr, mutation);
|
|
let element = self.as_element();
|
|
if let (&local_name!("nonce"), mutation) = (attr.local_name(), mutation) {
|
|
match mutation {
|
|
AttributeMutation::Set(..) => {
|
|
let nonce = &**attr.value();
|
|
element.update_nonce_internal_slot(nonce.to_owned());
|
|
},
|
|
AttributeMutation::Removed => {
|
|
element.update_nonce_internal_slot(String::new());
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SVGElementMethods<crate::DomTypeHolder> for SVGElement {
|
|
/// <https://html.spec.whatwg.org/multipage/#the-style-attribute>
|
|
fn Style(&self) -> DomRoot<CSSStyleDeclaration> {
|
|
self.style_decl.or_init(|| {
|
|
let global = self.owner_window();
|
|
CSSStyleDeclaration::new(
|
|
&global,
|
|
CSSStyleOwner::Element(Dom::from_ref(self.upcast())),
|
|
None,
|
|
CSSModificationAccess::ReadWrite,
|
|
CanGc::deprecated_note(),
|
|
)
|
|
})
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#globaleventhandlers
|
|
global_event_handlers!();
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
|
|
fn Nonce(&self) -> DOMString {
|
|
self.as_element().nonce_value().into()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-noncedelement-nonce>
|
|
fn SetNonce(&self, value: DOMString) {
|
|
self.as_element()
|
|
.update_nonce_internal_slot(value.to_string())
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
|
|
fn Autofocus(&self) -> bool {
|
|
self.element.has_attribute(&local_name!("autofocus"))
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-fe-autofocus>
|
|
fn SetAutofocus(&self, autofocus: bool, can_gc: CanGc) {
|
|
self.element
|
|
.set_bool_attribute(&local_name!("autofocus"), autofocus, can_gc);
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-focus>
|
|
fn Focus(&self, cx: &mut js::context::JSContext, options: &FocusOptions) {
|
|
// 1. If the allow focus steps given this's node document return false, then return.
|
|
// TODO: Implement this.
|
|
|
|
// 2. Run the focusing steps for this.
|
|
if !self
|
|
.upcast::<Node>()
|
|
.run_the_focusing_steps(None, CanGc::from_cx(cx))
|
|
{
|
|
// The specification seems to imply we should scroll into view even if this element
|
|
// is not a focusable area. No browser does this, so we return early in that case.
|
|
// See https://github.com/whatwg/html/issues/12231.
|
|
return;
|
|
}
|
|
|
|
// > 3. If options["focusVisible"] is true, or does not exist but in an
|
|
// > implementation-defined way the user agent determines it would be best to do so,
|
|
// > then indicate focus. TODO: Implement this.
|
|
// TODO: Implement this.
|
|
|
|
// > 4. If options["preventScroll"] is false, then scroll a target into view given this,
|
|
// > "auto", "center", and "center".
|
|
if !options.preventScroll {
|
|
let scroll_axis = ScrollAxisState {
|
|
position: ScrollLogicalPosition::Center,
|
|
requirement: ScrollRequirement::IfNotVisible,
|
|
};
|
|
self.upcast::<Element>().scroll_into_view_with_options(
|
|
ScrollBehavior::Smooth,
|
|
scroll_axis,
|
|
scroll_axis,
|
|
None,
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-blur>
|
|
fn Blur(&self, cx: &mut js::context::JSContext) {
|
|
// TODO: Run the unfocusing steps. Focus the top-level document, not
|
|
// the current document.
|
|
if !self.as_element().focus_state() {
|
|
return;
|
|
}
|
|
// <https://html.spec.whatwg.org/multipage/#unfocusing-steps>
|
|
self.owner_document()
|
|
.focus_handler()
|
|
.focus(FocusableArea::Viewport, CanGc::from_cx(cx));
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
|
|
fn TabIndex(&self) -> i32 {
|
|
self.element.tab_index()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-tabindex>
|
|
fn SetTabIndex(&self, tab_index: i32, can_gc: CanGc) {
|
|
self.element
|
|
.set_int_attribute(&local_name!("tabindex"), tab_index, can_gc);
|
|
}
|
|
}
|