Add support for trait-based virtual methods on Nodes, and use it for before_remove_attr and after_set_attr.

This commit is contained in:
Josh Matthews 2014-04-09 20:02:40 +02:00 committed by Ms2ger
parent ea2560ef20
commit ca6cfb5bca
9 changed files with 173 additions and 65 deletions

View file

@ -7,9 +7,7 @@
use dom::attr::Attr; use dom::attr::Attr;
use dom::attrlist::AttrList; use dom::attrlist::AttrList;
use dom::bindings::codegen::ElementBinding; use dom::bindings::codegen::ElementBinding;
use dom::bindings::codegen::InheritTypes::{ElementDerived, HTMLImageElementCast}; use dom::bindings::codegen::InheritTypes::{ElementDerived, NodeCast};
use dom::bindings::codegen::InheritTypes::{HTMLIFrameElementCast, NodeCast};
use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast;
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::utils::{Reflectable, Reflector}; use dom::bindings::utils::{Reflectable, Reflector};
use dom::bindings::error::{ErrorResult, Fallible, NamespaceError, InvalidCharacter}; use dom::bindings::error::{ErrorResult, Fallible, NamespaceError, InvalidCharacter};
@ -19,11 +17,9 @@ use dom::clientrect::ClientRect;
use dom::clientrectlist::ClientRectList; use dom::clientrectlist::ClientRectList;
use dom::document::Document; use dom::document::Document;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlimageelement::HTMLImageElement;
use dom::htmliframeelement::HTMLIFrameElement;
use dom::htmlobjectelement::HTMLObjectElement;
use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_from_node}; use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_from_node};
use dom::htmlserializer::serialize; use dom::htmlserializer::serialize;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery}; use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery};
use layout_interface::{ContentBoxesResponse, ContentChangedDocumentDamage}; use layout_interface::{ContentBoxesResponse, ContentChangedDocumentDamage};
use layout_interface::{MatchSelectorsDocumentDamage}; use layout_interface::{MatchSelectorsDocumentDamage};
@ -216,14 +212,6 @@ pub trait AttributeHandlers {
fn set_uint_attribute(&mut self, name: &str, value: u32); fn set_uint_attribute(&mut self, name: &str, value: u32);
} }
pub trait AfterSetAttrListener {
fn AfterSetAttr(&mut self, name: DOMString, value: DOMString);
}
pub trait BeforeRemoveAttrListener {
fn BeforeRemoveAttr(&mut self, name: DOMString);
}
impl AttributeHandlers for JS<Element> { impl AttributeHandlers for JS<Element> {
fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<JS<Attr>> { fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<JS<Attr>> {
if self.get().html_element_in_html_document() { if self.get().html_element_in_html_document() {
@ -397,23 +385,7 @@ impl AttributeHandlers for JS<Element> {
_ => () _ => ()
} }
//XXXjdm We really need something like a vtable so we can call AfterSetAttr. vtable_for(&node).after_set_attr(local_name.clone(), value.clone());
// This hardcoding is awful.
match node.type_id() {
ElementNodeTypeId(HTMLImageElementTypeId) => {
let mut elem: JS<HTMLImageElement> = HTMLImageElementCast::to(self).unwrap();
elem.AfterSetAttr(local_name.clone(), value.clone());
}
ElementNodeTypeId(HTMLIFrameElementTypeId) => {
let mut elem: JS<HTMLIFrameElement> = HTMLIFrameElementCast::to(self).unwrap();
elem.AfterSetAttr(local_name.clone(), value.clone());
}
ElementNodeTypeId(HTMLObjectElementTypeId) => {
let mut elem: JS<HTMLObjectElement> = HTMLObjectElementCast::to(self).unwrap();
elem.AfterSetAttr(local_name.clone(), value.clone());
}
_ => ()
}
self.notify_attribute_changed(local_name); self.notify_attribute_changed(local_name);
} }
@ -454,24 +426,12 @@ impl AttributeHandlers for JS<Element> {
// "borrowed value does not live long enough" // "borrowed value does not live long enough"
let mut doc = node.get().owner_doc().clone(); let mut doc = node.get().owner_doc().clone();
let doc = doc.get_mut(); let doc = doc.get_mut();
doc.unregister_named_element(self, old_value); doc.unregister_named_element(self, old_value.clone());
} }
_ => () _ => ()
} }
//XXXjdm We really need something like a vtable so we can call BeforeRemoveAttr. vtable_for(&node).before_remove_attr(local_name.clone(), old_value);
// This hardcoding is awful.
match node.type_id() {
ElementNodeTypeId(HTMLImageElementTypeId) => {
let mut elem: JS<HTMLImageElement> = HTMLImageElementCast::to(self).unwrap();
elem.BeforeRemoveAttr(local_name.clone());
}
ElementNodeTypeId(HTMLIFrameElementTypeId) => {
let mut elem: JS<HTMLIFrameElement> = HTMLIFrameElementCast::to(self).unwrap();
elem.BeforeRemoveAttr(local_name.clone());
}
_ => ()
}
self.notify_attribute_changed(local_name); self.notify_attribute_changed(local_name);
} }
@ -757,3 +717,10 @@ pub fn get_attribute_parts(name: DOMString) -> (Option<~str>, ~str) {
(prefix, local_name) (prefix, local_name)
} }
impl VirtualMethods for JS<Element> {
fn super_type(&self) -> Option<~VirtualMethods:> {
let node: JS<Node> = NodeCast::from(self);
Some(~node as ~VirtualMethods:)
}
}

View file

@ -9,6 +9,7 @@ use dom::bindings::codegen::EventListenerBinding::EventListener;
use dom::event::Event; use dom::event::Event;
use dom::eventdispatcher::dispatch_event; use dom::eventdispatcher::dispatch_event;
use dom::node::NodeTypeId; use dom::node::NodeTypeId;
use dom::virtualmethods::VirtualMethods;
use servo_util::str::DOMString; use servo_util::str::DOMString;
use collections::hashmap::HashMap; use collections::hashmap::HashMap;
@ -123,3 +124,9 @@ impl Reflectable for EventTarget {
&mut self.reflector_ &mut self.reflector_
} }
} }
impl VirtualMethods for JS<EventTarget> {
fn super_type(&self) -> Option<~VirtualMethods:> {
None
}
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::codegen::HTMLElementBinding; use dom::bindings::codegen::HTMLElementBinding;
use dom::bindings::codegen::InheritTypes::ElementCast;
use dom::bindings::codegen::InheritTypes::HTMLElementDerived; use dom::bindings::codegen::InheritTypes::HTMLElementDerived;
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::error::{ErrorResult, Fallible}; use dom::bindings::error::{ErrorResult, Fallible};
@ -10,6 +11,7 @@ use dom::document::Document;
use dom::element::{Element, ElementTypeId, HTMLElementTypeId}; use dom::element::{Element, ElementTypeId, HTMLElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::node::{Node, ElementNodeTypeId}; use dom::node::{Node, ElementNodeTypeId};
use dom::virtualmethods::VirtualMethods;
use js::jsapi::JSContext; use js::jsapi::JSContext;
use js::jsval::{JSVal, NullValue}; use js::jsval::{JSVal, NullValue};
use servo_util::namespace; use servo_util::namespace;
@ -160,3 +162,10 @@ impl HTMLElement {
0 0
} }
} }
impl VirtualMethods for JS<HTMLElement> {
fn super_type(&self) -> Option<~VirtualMethods:> {
let element: JS<Element> = ElementCast::from(self);
Some(~element as ~VirtualMethods:)
}
}

View file

@ -3,15 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::codegen::HTMLIFrameElementBinding; use dom::bindings::codegen::HTMLIFrameElementBinding;
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementDerived}; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementDerived, HTMLElementCast};
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::error::ErrorResult; use dom::bindings::error::ErrorResult;
use dom::document::Document; use dom::document::Document;
use dom::element::{HTMLIFrameElementTypeId, Element}; use dom::element::{HTMLIFrameElementTypeId, Element};
use dom::element::{AttributeHandlers, AfterSetAttrListener, BeforeRemoveAttrListener}; use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId}; use dom::node::{Node, ElementNodeTypeId};
use dom::virtualmethods::VirtualMethods;
use dom::windowproxy::WindowProxy; use dom::windowproxy::WindowProxy;
use servo_util::str::DOMString; use servo_util::str::DOMString;
@ -210,8 +211,18 @@ impl HTMLIFrameElement {
} }
} }
impl AfterSetAttrListener for JS<HTMLIFrameElement> { impl VirtualMethods for JS<HTMLIFrameElement> {
fn AfterSetAttr(&mut self, name: DOMString, value: DOMString) { fn super_type(&self) -> Option<~VirtualMethods:> {
let htmlelement: JS<HTMLElement> = HTMLElementCast::from(self);
Some(~htmlelement as ~VirtualMethods:)
}
fn after_set_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.after_set_attr(name.clone(), value.clone()),
_ => (),
}
if "sandbox" == name { if "sandbox" == name {
let mut modes = AllowNothing as u8; let mut modes = AllowNothing as u8;
for word in value.split(' ') { for word in value.split(' ') {
@ -230,10 +241,13 @@ impl AfterSetAttrListener for JS<HTMLIFrameElement> {
self.get_mut().sandbox = Some(modes); self.get_mut().sandbox = Some(modes);
} }
} }
fn before_remove_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.before_remove_attr(name.clone(), value),
_ => (),
} }
impl BeforeRemoveAttrListener for JS<HTMLIFrameElement> {
fn BeforeRemoveAttr(&mut self, name: DOMString) {
if "sandbox" == name { if "sandbox" == name {
self.get_mut().sandbox = None; self.get_mut().sandbox = None;
} }

View file

@ -4,15 +4,16 @@
use dom::bindings::codegen::HTMLImageElementBinding; use dom::bindings::codegen::HTMLImageElementBinding;
use dom::bindings::codegen::InheritTypes::{NodeCast, HTMLImageElementDerived}; use dom::bindings::codegen::InheritTypes::{NodeCast, HTMLImageElementDerived};
use dom::bindings::codegen::InheritTypes::{ElementCast}; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::error::ErrorResult; use dom::bindings::error::ErrorResult;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, HTMLImageElementTypeId}; use dom::element::{Element, HTMLImageElementTypeId};
use dom::element::{AttributeHandlers, AfterSetAttrListener, BeforeRemoveAttrListener}; use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node}; use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node};
use dom::virtualmethods::VirtualMethods;
use servo_util::geometry::to_px; use servo_util::geometry::to_px;
use layout_interface::{ContentBoxQuery, ContentBoxResponse}; use layout_interface::{ContentBoxQuery, ContentBoxResponse};
use servo_net::image_cache_task; use servo_net::image_cache_task;
@ -226,18 +227,31 @@ impl HTMLImageElement {
} }
} }
impl AfterSetAttrListener for JS<HTMLImageElement> { impl VirtualMethods for JS<HTMLImageElement> {
fn AfterSetAttr(&mut self, name: DOMString, value: DOMString) { fn super_type(&self) -> Option<~VirtualMethods:> {
let htmlelement: JS<HTMLElement> = HTMLElementCast::from(self);
Some(~htmlelement as ~VirtualMethods:)
}
fn after_set_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.after_set_attr(name.clone(), value.clone()),
_ => (),
}
if "src" == name { if "src" == name {
let window = window_from_node(self); let window = window_from_node(self);
let url = Some(window.get().get_url()); let url = Some(window.get().get_url());
self.get_mut().update_image(Some(value), url); self.get_mut().update_image(Some(value), url);
} }
} }
fn before_remove_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.before_remove_attr(name.clone(), value.clone()),
_ => (),
} }
impl BeforeRemoveAttrListener for JS<HTMLImageElement> {
fn BeforeRemoveAttr(&mut self, name: DOMString) {
if "src" == name { if "src" == name {
self.get_mut().update_image(None, None); self.get_mut().update_image(None, None);
} }

View file

@ -4,17 +4,18 @@
use dom::bindings::codegen::HTMLObjectElementBinding; use dom::bindings::codegen::HTMLObjectElementBinding;
use dom::bindings::codegen::InheritTypes::HTMLObjectElementDerived; use dom::bindings::codegen::InheritTypes::HTMLObjectElementDerived;
use dom::bindings::codegen::InheritTypes::ElementCast; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::error::ErrorResult; use dom::bindings::error::ErrorResult;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, HTMLObjectElementTypeId}; use dom::element::{Element, HTMLObjectElementTypeId};
use dom::element::{AttributeHandlers, AfterSetAttrListener}; use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::htmlformelement::HTMLFormElement; use dom::htmlformelement::HTMLFormElement;
use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node}; use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node};
use dom::validitystate::ValidityState; use dom::validitystate::ValidityState;
use dom::virtualmethods::VirtualMethods;
use dom::windowproxy::WindowProxy; use dom::windowproxy::WindowProxy;
use servo_util::str::DOMString; use servo_util::str::DOMString;
@ -244,8 +245,18 @@ impl HTMLObjectElement {
} }
} }
impl AfterSetAttrListener for JS<HTMLObjectElement> { impl VirtualMethods for JS<HTMLObjectElement> {
fn AfterSetAttr(&mut self, name: DOMString, _value: DOMString) { fn super_type(&self) -> Option<~VirtualMethods:> {
let htmlelement: JS<HTMLElement> = HTMLElementCast::from(self);
Some(~htmlelement as ~VirtualMethods:)
}
fn after_set_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.after_set_attr(name.clone(), value),
_ => (),
}
if "data" == name { if "data" == name {
let window = window_from_node(self); let window = window_from_node(self);
let url = Some(window.get().get_url()); let url = Some(window.get().get_url());

View file

@ -8,7 +8,7 @@ use dom::attr::Attr;
use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast}; use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived}; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived};
use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast; use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, EventTargetCast};
use dom::bindings::codegen::NodeBinding::NodeConstants; use dom::bindings::codegen::NodeBinding::NodeConstants;
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
@ -22,8 +22,9 @@ use dom::documenttype::DocumentType;
use dom::element::{Element, ElementTypeId, HTMLAnchorElementTypeId, IElement}; use dom::element::{Element, ElementTypeId, HTMLAnchorElementTypeId, IElement};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::nodelist::{NodeList}; use dom::nodelist::{NodeList};
use dom::text::Text;
use dom::processinginstruction::ProcessingInstruction; use dom::processinginstruction::ProcessingInstruction;
use dom::text::Text;
use dom::virtualmethods::VirtualMethods;
use dom::window::Window; use dom::window::Window;
use html::hubbub_html_parser::build_element_from_tag; use html::hubbub_html_parser::build_element_from_tag;
use layout_interface::{LayoutChan, ReapLayoutDataMsg, UntrustedNodeAddress}; use layout_interface::{LayoutChan, ReapLayoutDataMsg, UntrustedNodeAddress};
@ -1834,3 +1835,10 @@ pub fn window_from_node<T: NodeBase>(derived: &JS<T>) -> JS<Window> {
let document: JS<Document> = document_from_node(derived); let document: JS<Document> = document_from_node(derived);
document.get().window.clone() document.get().window.clone()
} }
impl VirtualMethods for JS<Node> {
fn super_type(&self) -> Option<~VirtualMethods:> {
let eventtarget: JS<EventTarget> = EventTargetCast::from(self);
Some(~eventtarget as ~VirtualMethods:)
}
}

View file

@ -0,0 +1,77 @@
/* 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 http://mozilla.org/MPL/2.0/. */
use dom::bindings::codegen::InheritTypes::ElementCast;
use dom::bindings::codegen::InheritTypes::HTMLElementCast;
use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast;
use dom::bindings::codegen::InheritTypes::HTMLImageElementCast;
use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast;
use dom::bindings::js::JS;
use dom::element::Element;
use dom::element::{ElementTypeId, HTMLImageElementTypeId};
use dom::element::{HTMLIFrameElementTypeId, HTMLObjectElementTypeId};
use dom::htmlelement::HTMLElement;
use dom::htmliframeelement::HTMLIFrameElement;
use dom::htmlimageelement::HTMLImageElement;
use dom::htmlobjectelement::HTMLObjectElement;
use dom::node::{Node, ElementNodeTypeId};
use servo_util::str::DOMString;
/// Trait to allow DOM nodes to opt-in to overriding (or adding to) common
/// behaviours. Replicates the effect of C++ virtual methods.
pub trait VirtualMethods {
/// Returns self as the superclass of the implementation for this trait,
/// if any.
fn super_type(&self) -> Option<~VirtualMethods:>;
/// Called when changing or adding attributes, after the attribute's value
/// has been updated.
fn after_set_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.after_set_attr(name, value),
_ => (),
}
}
/// Called when changing or removing attributes, before any modification
/// has taken place.
fn before_remove_attr(&mut self, name: DOMString, value: DOMString) {
match self.super_type() {
Some(ref mut s) => s.before_remove_attr(name, value),
_ => (),
}
}
}
/// Obtain a VirtualMethods instance for a given Node-derived object. Any
/// method call on the trait object will invoke the corresponding method on the
/// concrete type, propagating up the parent hierarchy unless otherwise
/// interrupted.
pub fn vtable_for<'a>(node: &JS<Node>) -> ~VirtualMethods: {
match node.get().type_id {
ElementNodeTypeId(HTMLImageElementTypeId) => {
let element: JS<HTMLImageElement> = HTMLImageElementCast::to(node).unwrap();
~element as ~VirtualMethods:
}
ElementNodeTypeId(HTMLIFrameElementTypeId) => {
let element: JS<HTMLIFrameElement> = HTMLIFrameElementCast::to(node).unwrap();
~element as ~VirtualMethods:
}
ElementNodeTypeId(HTMLObjectElementTypeId) => {
let element: JS<HTMLObjectElement> = HTMLObjectElementCast::to(node).unwrap();
~element as ~VirtualMethods:
}
ElementNodeTypeId(ElementTypeId) => {
let element: JS<Element> = ElementCast::to(node).unwrap();
~element as ~VirtualMethods:
}
ElementNodeTypeId(_) => {
let element: JS<HTMLElement> = HTMLElementCast::to(node).unwrap();
~element as ~VirtualMethods:
}
_ => {
~node.clone() as ~VirtualMethods:
}
}
}

View file

@ -153,6 +153,7 @@ pub mod dom {
pub mod uievent; pub mod uievent;
pub mod text; pub mod text;
pub mod validitystate; pub mod validitystate;
pub mod virtualmethods;
pub mod window; pub mod window;
pub mod windowproxy; pub mod windowproxy;