mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Auto merge of #8506 - nox:finish-ranges, r=dzbarsky
Properly propagate changes when range or trees are mutated Does the same thing as #6817, but storing Range instances directly in their start and end containers. Cc @dzbarsky <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/8506) <!-- Reviewable:end -->
This commit is contained in:
commit
89ab368258
20 changed files with 669 additions and 4708 deletions
|
@ -18,6 +18,10 @@ DOMInterfaces = {
|
|||
'outerObjectHook': 'Some(bindings::utils::outerize_global)',
|
||||
},
|
||||
|
||||
'Range': {
|
||||
'weakReferenceable': True,
|
||||
},
|
||||
|
||||
#FIXME(jdm): This should be 'register': False, but then we don't generate enum types
|
||||
'TestBinding': {},
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ use js::jsapi::{JSTracer, JS_GetReservedSlot, JS_SetReservedSlot};
|
|||
use js::jsval::PrivateValue;
|
||||
use libc::c_void;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::iter::Iterator;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut, Drop};
|
||||
use util::mem::HeapSizeOf;
|
||||
|
||||
/// The index of the slot wherein a pointer to the weak holder cell is
|
||||
|
@ -113,6 +115,25 @@ impl<T: WeakReferenceable> HeapSizeOf for WeakRef<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: WeakReferenceable> PartialEq for WeakRef<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
(**self.ptr).value.get() == (**other.ptr).value.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WeakReferenceable> PartialEq<T> for WeakRef<T> {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
unsafe {
|
||||
match (**self.ptr).value.get() {
|
||||
Some(ptr) => *ptr == other,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
no_jsmanaged_fields!(WeakRef<T: WeakReferenceable>);
|
||||
|
||||
impl<T: WeakReferenceable> Drop for WeakRef<T> {
|
||||
|
@ -182,3 +203,81 @@ impl<T: WeakReferenceable> JSTraceable for MutableWeakRef<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A vector of weak references. On tracing, the vector retains
|
||||
/// only references which still point to live objects.
|
||||
#[allow_unrooted_interior]
|
||||
#[derive(HeapSizeOf)]
|
||||
pub struct WeakRefVec<T: WeakReferenceable> {
|
||||
vec: Vec<WeakRef<T>>,
|
||||
}
|
||||
|
||||
impl<T: WeakReferenceable> WeakRefVec<T> {
|
||||
/// Create a new vector of weak references.
|
||||
pub fn new() -> Self {
|
||||
WeakRefVec { vec: vec![] }
|
||||
}
|
||||
|
||||
/// Calls a function on each reference which still points to a
|
||||
/// live object. The order of the references isn't preserved.
|
||||
pub fn update<F: FnMut(WeakRefEntry<T>)>(&mut self, mut f: F) {
|
||||
let mut i = 0;
|
||||
while i < self.vec.len() {
|
||||
if self.vec[i].is_alive() {
|
||||
f(WeakRefEntry { vec: self, index: &mut i });
|
||||
} else {
|
||||
self.vec.swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the vector of its dead references.
|
||||
pub fn retain_alive(&mut self) {
|
||||
self.update(|_| ());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WeakReferenceable> Deref for WeakRefVec<T> {
|
||||
type Target = Vec<WeakRef<T>>;
|
||||
|
||||
fn deref(&self) -> &Vec<WeakRef<T>> {
|
||||
&self.vec
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WeakReferenceable> DerefMut for WeakRefVec<T> {
|
||||
fn deref_mut(&mut self) -> &mut Vec<WeakRef<T>> {
|
||||
&mut self.vec
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry of a vector of weak references. Passed to the closure
|
||||
/// given to `WeakRefVec::update`.
|
||||
#[allow_unrooted_interior]
|
||||
pub struct WeakRefEntry<'a, T: WeakReferenceable + 'a> {
|
||||
vec: &'a mut WeakRefVec<T>,
|
||||
index: &'a mut usize,
|
||||
}
|
||||
|
||||
impl<'a, T: WeakReferenceable + 'a> WeakRefEntry<'a, T> {
|
||||
/// Remove the entry from the underlying vector of weak references.
|
||||
pub fn remove(self) -> WeakRef<T> {
|
||||
let ref_ = self.vec.swap_remove(*self.index);
|
||||
mem::forget(self);
|
||||
ref_
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: WeakReferenceable + 'a> Deref for WeakRefEntry<'a, T> {
|
||||
type Target = WeakRef<T>;
|
||||
|
||||
fn deref(&self) -> &WeakRef<T> {
|
||||
&self.vec[*self.index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: WeakReferenceable + 'a> Drop for WeakRefEntry<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
*self.index += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,18 @@
|
|||
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
|
||||
use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
|
||||
use dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId};
|
||||
use dom::bindings::codegen::UnionTypes::NodeOrString;
|
||||
use dom::bindings::error::{Error, ErrorResult, Fallible};
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::{LayoutJS, Root};
|
||||
use dom::comment::Comment;
|
||||
use dom::document::Document;
|
||||
use dom::element::Element;
|
||||
use dom::node::{Node, NodeDamage};
|
||||
use dom::processinginstruction::ProcessingInstruction;
|
||||
use dom::text::Text;
|
||||
use std::cell::Ref;
|
||||
use util::str::DOMString;
|
||||
|
||||
|
@ -30,6 +35,38 @@ impl CharacterData {
|
|||
data: DOMRefCell::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_with_data(&self, data: DOMString, document: &Document) -> Root<Node> {
|
||||
match self.upcast::<Node>().type_id() {
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
|
||||
Root::upcast(Comment::new(data, &document))
|
||||
}
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
|
||||
let pi = self.downcast::<ProcessingInstruction>().unwrap();
|
||||
Root::upcast(ProcessingInstruction::new(pi.Target(), data, &document))
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
|
||||
Root::upcast(Text::new(data, &document))
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data(&self) -> Ref<DOMString> {
|
||||
self.data.borrow()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn append_data(&self, data: &str) {
|
||||
self.data.borrow_mut().push_str(data);
|
||||
self.content_changed();
|
||||
}
|
||||
|
||||
fn content_changed(&self) {
|
||||
let node = self.upcast::<Node>();
|
||||
node.owner_doc().content_changed(node, NodeDamage::OtherNodeDamage);
|
||||
}
|
||||
}
|
||||
|
||||
impl CharacterDataMethods for CharacterData {
|
||||
|
@ -40,13 +77,17 @@ impl CharacterDataMethods for CharacterData {
|
|||
|
||||
// https://dom.spec.whatwg.org/#dom-characterdata-data
|
||||
fn SetData(&self, data: DOMString) {
|
||||
let old_length = self.Length();
|
||||
let new_length = data.utf16_units().count() as u32;
|
||||
*self.data.borrow_mut() = data;
|
||||
self.content_changed();
|
||||
let node = self.upcast::<Node>();
|
||||
node.ranges().replace_code_units(node, 0, old_length, new_length);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-characterdata-length
|
||||
fn Length(&self) -> u32 {
|
||||
self.data.borrow().chars().map(|c| c.len_utf16()).sum::<usize>() as u32
|
||||
self.data.borrow().utf16_units().count() as u32
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-characterdata-substringdata
|
||||
|
@ -107,7 +148,10 @@ impl CharacterDataMethods for CharacterData {
|
|||
};
|
||||
*self.data.borrow_mut() = DOMString::from(new_data);
|
||||
self.content_changed();
|
||||
// FIXME: Once we have `Range`, we should implement step 8 to step 11
|
||||
// Steps 8-11.
|
||||
let node = self.upcast::<Node>();
|
||||
node.ranges().replace_code_units(
|
||||
node, offset, count, arg.utf16_units().count() as u32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -143,24 +187,6 @@ impl CharacterDataMethods for CharacterData {
|
|||
}
|
||||
}
|
||||
|
||||
impl CharacterData {
|
||||
#[inline]
|
||||
pub fn data(&self) -> Ref<DOMString> {
|
||||
self.data.borrow()
|
||||
}
|
||||
#[inline]
|
||||
pub fn append_data(&self, data: &str) {
|
||||
// FIXME(ajeffrey): Efficient append on DOMStrings?
|
||||
self.data.borrow_mut().push_str(data);
|
||||
self.content_changed();
|
||||
}
|
||||
|
||||
fn content_changed(&self) {
|
||||
let node = self.upcast::<Node>();
|
||||
node.owner_doc().content_changed(node, NodeDamage::OtherNodeDamage);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub trait LayoutCharacterDataHelpers {
|
||||
unsafe fn data_for_layout(&self) -> &str;
|
||||
|
|
|
@ -53,7 +53,7 @@ use dom::htmltemplateelement::HTMLTemplateElement;
|
|||
use dom::htmltextareaelement::{HTMLTextAreaElement, RawLayoutHTMLTextAreaElementHelpers};
|
||||
use dom::namednodemap::NamedNodeMap;
|
||||
use dom::node::{CLICK_IN_PROGRESS, LayoutNodeHelpers, Node};
|
||||
use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE};
|
||||
use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE, UnbindContext};
|
||||
use dom::node::{document_from_node, window_from_node};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::text::Text;
|
||||
|
@ -1615,12 +1615,10 @@ impl VirtualMethods for Element {
|
|||
}
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
if !tree_in_doc {
|
||||
if !context.tree_in_doc {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use dom::bindings::js::Root;
|
|||
use dom::document::Document;
|
||||
use dom::element::{AttributeMutation, Element};
|
||||
use dom::htmlelement::HTMLElement;
|
||||
use dom::node::{Node, document_from_node};
|
||||
use dom::node::{Node, UnbindContext, document_from_node};
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use string_cache::Atom;
|
||||
use url::Url;
|
||||
|
@ -77,8 +77,8 @@ impl VirtualMethods for HTMLBaseElement {
|
|||
self.bind_unbind(tree_in_doc);
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
self.super_type().unwrap().unbind_from_tree(tree_in_doc);
|
||||
self.bind_unbind(tree_in_doc);
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
self.bind_unbind(context.tree_in_doc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use dom::htmlelement::HTMLElement;
|
|||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||
use dom::htmlformelement::{FormControl, FormSubmitter};
|
||||
use dom::htmlformelement::{SubmittedFrom, HTMLFormElement};
|
||||
use dom::node::{Node, document_from_node, window_from_node};
|
||||
use dom::node::{Node, UnbindContext, document_from_node, window_from_node};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::validitystate::ValidityState;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
|
@ -174,10 +174,8 @@ impl VirtualMethods for HTMLButtonElement {
|
|||
self.upcast::<Element>().check_ancestors_disabled_state_for_form_control();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
let node = self.upcast::<Node>();
|
||||
let el = self.upcast::<Element>();
|
||||
|
|
|
@ -20,7 +20,7 @@ use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
|
|||
use dom::event::Event;
|
||||
use dom::eventtarget::EventTarget;
|
||||
use dom::htmlelement::HTMLElement;
|
||||
use dom::node::{Node, window_from_node};
|
||||
use dom::node::{Node, UnbindContext, window_from_node};
|
||||
use dom::urlhelper::UrlHelper;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use dom::window::Window;
|
||||
|
@ -483,10 +483,8 @@ impl VirtualMethods for HTMLIFrameElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
|
||||
if let Some(pipeline_id) = self.pipeline_id.get() {
|
||||
|
|
|
@ -23,7 +23,7 @@ use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
|||
use dom::htmlformelement::{FormControl, FormDatum, FormSubmitter, HTMLFormElement};
|
||||
use dom::htmlformelement::{ResetFrom, SubmittedFrom};
|
||||
use dom::keyboardevent::KeyboardEvent;
|
||||
use dom::node::{Node, NodeDamage};
|
||||
use dom::node::{Node, NodeDamage, UnbindContext};
|
||||
use dom::node::{document_from_node, window_from_node};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
|
@ -645,10 +645,8 @@ impl VirtualMethods for HTMLInputElement {
|
|||
self.upcast::<Element>().check_ancestors_disabled_state_for_form_control();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
let node = self.upcast::<Node>();
|
||||
let el = self.upcast::<Element>();
|
||||
|
|
|
@ -15,7 +15,7 @@ use dom::element::{AttributeMutation, Element};
|
|||
use dom::htmlelement::HTMLElement;
|
||||
use dom::htmlscriptelement::HTMLScriptElement;
|
||||
use dom::htmlselectelement::HTMLSelectElement;
|
||||
use dom::node::Node;
|
||||
use dom::node::{Node, UnbindContext};
|
||||
use dom::text::Text;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use selectors::states::*;
|
||||
|
@ -207,10 +207,8 @@ impl VirtualMethods for HTMLOptionElement {
|
|||
self.pick_if_selected_and_reset();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
let node = self.upcast::<Node>();
|
||||
let el = self.upcast::<Element>();
|
||||
|
|
|
@ -16,7 +16,7 @@ use dom::htmlelement::HTMLElement;
|
|||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||
use dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||
use dom::htmloptionelement::HTMLOptionElement;
|
||||
use dom::node::{Node, window_from_node};
|
||||
use dom::node::{Node, UnbindContext, window_from_node};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::validitystate::ValidityState;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
|
@ -196,10 +196,8 @@ impl VirtualMethods for HTMLSelectElement {
|
|||
self.upcast::<Element>().check_ancestors_disabled_state_for_form_control();
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
let node = self.upcast::<Node>();
|
||||
let el = self.upcast::<Element>();
|
||||
|
|
|
@ -20,7 +20,7 @@ use dom::htmlelement::HTMLElement;
|
|||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||
use dom::htmlformelement::{FormControl, HTMLFormElement};
|
||||
use dom::keyboardevent::KeyboardEvent;
|
||||
use dom::node::{ChildrenMutation, Node, NodeDamage};
|
||||
use dom::node::{ChildrenMutation, Node, NodeDamage, UnbindContext};
|
||||
use dom::node::{document_from_node, window_from_node};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
|
@ -300,10 +300,8 @@ impl VirtualMethods for HTMLTextAreaElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
}
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
|
||||
let node = self.upcast::<Node>();
|
||||
let el = self.upcast::<Element>();
|
||||
|
|
|
@ -33,7 +33,6 @@ use dom::bindings::trace::JSTraceable;
|
|||
use dom::bindings::trace::RootedVec;
|
||||
use dom::bindings::xmlname::namespace_from_domstring;
|
||||
use dom::characterdata::CharacterData;
|
||||
use dom::comment::Comment;
|
||||
use dom::document::{Document, DocumentSource, IsHTMLDocument};
|
||||
use dom::documentfragment::DocumentFragment;
|
||||
use dom::documenttype::DocumentType;
|
||||
|
@ -43,6 +42,7 @@ use dom::htmlcollection::HTMLCollection;
|
|||
use dom::htmlelement::HTMLElement;
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::processinginstruction::ProcessingInstruction;
|
||||
use dom::range::WeakRangeVec;
|
||||
use dom::text::Text;
|
||||
use dom::virtualmethods::{VirtualMethods, vtable_for};
|
||||
use dom::window::Window;
|
||||
|
@ -109,6 +109,12 @@ pub struct Node {
|
|||
/// The maximum version of any inclusive descendant of this node.
|
||||
inclusive_descendants_version: Cell<u64>,
|
||||
|
||||
/// A vector of weak references to Range instances of which the start
|
||||
/// or end containers are this node. No range should ever be found
|
||||
/// twice in this vector, even if both the start and end containers
|
||||
/// are this node.
|
||||
ranges: WeakRangeVec,
|
||||
|
||||
/// Layout information. Only the layout task may touch this data.
|
||||
///
|
||||
/// Must be sent back to the layout task to be destroyed when this
|
||||
|
@ -283,6 +289,7 @@ impl Node {
|
|||
}
|
||||
|
||||
new_child.parent_node.set(Some(self));
|
||||
self.children_count.set(self.children_count.get() + 1);
|
||||
|
||||
let parent_in_doc = self.is_in_doc();
|
||||
for node in new_child.traverse_preorder() {
|
||||
|
@ -296,7 +303,7 @@ impl Node {
|
|||
/// Removes the given child from this node's list of children.
|
||||
///
|
||||
/// Fails unless `child` is a child of this node.
|
||||
fn remove_child(&self, child: &Node) {
|
||||
fn remove_child(&self, child: &Node, cached_index: Option<u32>) {
|
||||
assert!(child.parent_node.get().r() == Some(self));
|
||||
let prev_sibling = child.GetPreviousSibling();
|
||||
match prev_sibling {
|
||||
|
@ -317,14 +324,16 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
let context = UnbindContext::new(self, prev_sibling.r(), cached_index);
|
||||
|
||||
child.prev_sibling.set(None);
|
||||
child.next_sibling.set(None);
|
||||
child.parent_node.set(None);
|
||||
self.children_count.set(self.children_count.get() - 1);
|
||||
|
||||
let parent_in_doc = self.is_in_doc();
|
||||
for node in child.traverse_preorder() {
|
||||
node.set_flag(IS_IN_DOC, false);
|
||||
vtable_for(&&*node).unbind_from_tree(parent_in_doc);
|
||||
vtable_for(&&*node).unbind_from_tree(&context);
|
||||
node.layout_data.dispose(&node);
|
||||
}
|
||||
|
||||
|
@ -433,6 +442,10 @@ impl Node {
|
|||
self.children_count.get()
|
||||
}
|
||||
|
||||
pub fn ranges(&self) -> &WeakRangeVec {
|
||||
&self.ranges
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_doctype(&self) -> bool {
|
||||
self.type_id() == NodeTypeId::DocumentType
|
||||
|
@ -1307,6 +1320,7 @@ impl Node {
|
|||
children_count: Cell::new(0u32),
|
||||
flags: Cell::new(flags),
|
||||
inclusive_descendants_version: Cell::new(0),
|
||||
ranges: WeakRangeVec::new(),
|
||||
|
||||
layout_data: LayoutDataRef::new(),
|
||||
|
||||
|
@ -1481,7 +1495,20 @@ impl Node {
|
|||
debug_assert!(&*node.owner_doc() == &*parent.owner_doc());
|
||||
debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r()));
|
||||
|
||||
// Steps 1-2: ranges.
|
||||
// Step 1.
|
||||
let count = if node.is::<DocumentFragment>() {
|
||||
node.children_count()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
// Step 2.
|
||||
if let Some(child) = child {
|
||||
if !parent.ranges.is_empty() {
|
||||
let index = child.index();
|
||||
// Steps 2.1-2.
|
||||
parent.ranges.increase_above(parent, index, count);
|
||||
}
|
||||
}
|
||||
let mut new_nodes = RootedVec::new();
|
||||
let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() {
|
||||
// Step 3.
|
||||
|
@ -1571,18 +1598,31 @@ impl Node {
|
|||
// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) {
|
||||
assert!(node.GetParentNode().map_or(false, |node_parent| node_parent.r() == parent));
|
||||
|
||||
// Step 1-5: ranges.
|
||||
let cached_index = {
|
||||
if parent.ranges.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Step 1.
|
||||
let index = node.index();
|
||||
// Steps 2-3 are handled in Node::unbind_from_tree.
|
||||
// Steps 4-5.
|
||||
parent.ranges.decrease_above(parent, index, 1);
|
||||
// Parent had ranges, we needed the index, let's keep track of
|
||||
// it to avoid computing it for other ranges when calling
|
||||
// unbind_from_tree recursively.
|
||||
Some(index)
|
||||
}
|
||||
};
|
||||
// Step 6.
|
||||
let old_previous_sibling = node.GetPreviousSibling();
|
||||
// Steps 7-8: mutation observers.
|
||||
// Step 9.
|
||||
let old_next_sibling = node.GetNextSibling();
|
||||
parent.remove_child(node);
|
||||
parent.remove_child(node, cached_index);
|
||||
if let SuppressObserver::Unsuppressed = suppress_observers {
|
||||
vtable_for(&parent).children_changed(
|
||||
&ChildrenMutation::replace(old_previous_sibling.r(),
|
||||
&node, &[],
|
||||
&Some(&node), &[],
|
||||
old_next_sibling.r()));
|
||||
}
|
||||
}
|
||||
|
@ -1611,10 +1651,9 @@ impl Node {
|
|||
let doc_fragment = DocumentFragment::new(document.r());
|
||||
Root::upcast::<Node>(doc_fragment)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
|
||||
NodeTypeId::CharacterData(_) => {
|
||||
let cdata = node.downcast::<CharacterData>().unwrap();
|
||||
let comment = Comment::new(cdata.Data(), document.r());
|
||||
Root::upcast::<Node>(comment)
|
||||
cdata.clone_with_data(cdata.Data(), &document)
|
||||
},
|
||||
NodeTypeId::Document(_) => {
|
||||
let document = node.downcast::<Document>().unwrap();
|
||||
|
@ -1640,17 +1679,6 @@ impl Node {
|
|||
document.r(), ElementCreator::ScriptCreated);
|
||||
Root::upcast::<Node>(element)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text) => {
|
||||
let cdata = node.downcast::<CharacterData>().unwrap();
|
||||
let text = Text::new(cdata.Data(), document.r());
|
||||
Root::upcast::<Node>(text)
|
||||
},
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
|
||||
let pi = node.downcast::<ProcessingInstruction>().unwrap();
|
||||
let pi = ProcessingInstruction::new(pi.Target(),
|
||||
pi.upcast::<CharacterData>().Data(), document.r());
|
||||
Root::upcast::<Node>(pi)
|
||||
},
|
||||
};
|
||||
|
||||
// Step 3.
|
||||
|
@ -2038,11 +2066,6 @@ impl NodeMethods for Node {
|
|||
}
|
||||
}
|
||||
|
||||
// Ok if not caught by previous error checks.
|
||||
if node == child {
|
||||
return Ok(Root::from_ref(child));
|
||||
}
|
||||
|
||||
// Step 7-8.
|
||||
let child_next_sibling = child.GetNextSibling();
|
||||
let node_next_sibling = node.GetNextSibling();
|
||||
|
@ -2053,14 +2076,19 @@ impl NodeMethods for Node {
|
|||
};
|
||||
|
||||
// Step 9.
|
||||
let previous_sibling = child.GetPreviousSibling();
|
||||
|
||||
// Step 10.
|
||||
let document = document_from_node(self);
|
||||
Node::adopt(node, document.r());
|
||||
|
||||
// Step 10.
|
||||
let previous_sibling = child.GetPreviousSibling();
|
||||
|
||||
let removed_child = if node != child {
|
||||
// Step 11.
|
||||
Node::remove(child, self, SuppressObserver::Suppressed);
|
||||
Some(child)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Step 12.
|
||||
let mut nodes = RootedVec::new();
|
||||
|
@ -2077,7 +2105,7 @@ impl NodeMethods for Node {
|
|||
// Step 14.
|
||||
vtable_for(&self).children_changed(
|
||||
&ChildrenMutation::replace(previous_sibling.r(),
|
||||
&child, nodes,
|
||||
&removed_child, nodes,
|
||||
reference_child));
|
||||
|
||||
// Step 15.
|
||||
|
@ -2092,28 +2120,26 @@ impl NodeMethods for Node {
|
|||
|
||||
// https://dom.spec.whatwg.org/#dom-node-normalize
|
||||
fn Normalize(&self) {
|
||||
let mut prev_text: Option<Root<Text>> = None;
|
||||
for child in self.children() {
|
||||
match child.downcast::<Text>() {
|
||||
Some(text) => {
|
||||
let characterdata = text.upcast::<CharacterData>();
|
||||
if characterdata.Length() == 0 {
|
||||
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
|
||||
let mut children = self.children().enumerate().peekable();
|
||||
while let Some((_, node)) = children.next() {
|
||||
if let Some(text) = node.downcast::<Text>() {
|
||||
let cdata = text.upcast::<CharacterData>();
|
||||
let mut length = cdata.Length();
|
||||
if length == 0 {
|
||||
Node::remove(&node, self, SuppressObserver::Unsuppressed);
|
||||
continue;
|
||||
}
|
||||
while children.peek().map_or(false, |&(_, ref sibling)| sibling.is::<Text>()) {
|
||||
let (index, sibling) = children.next().unwrap();
|
||||
sibling.ranges.drain_to_preceding_text_sibling(&sibling, &node, length);
|
||||
self.ranges.move_to_text_child_at(self, index as u32, &node, length as u32);
|
||||
let sibling_cdata = sibling.downcast::<CharacterData>().unwrap();
|
||||
length += sibling_cdata.Length();
|
||||
cdata.append_data(&sibling_cdata.data());
|
||||
Node::remove(&sibling, self, SuppressObserver::Unsuppressed);
|
||||
}
|
||||
} else {
|
||||
match prev_text {
|
||||
Some(ref text_node) => {
|
||||
let prev_characterdata = text_node.upcast::<CharacterData>();
|
||||
prev_characterdata.append_data(&**characterdata.data());
|
||||
Node::remove(&*child, self, SuppressObserver::Unsuppressed);
|
||||
},
|
||||
None => prev_text = Some(Root::from_ref(text))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
child.Normalize();
|
||||
prev_text = None;
|
||||
}
|
||||
node.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2348,25 +2374,17 @@ impl VirtualMethods for Node {
|
|||
if let Some(ref s) = self.super_type() {
|
||||
s.children_changed(mutation);
|
||||
}
|
||||
match *mutation {
|
||||
ChildrenMutation::Append { added, .. } |
|
||||
ChildrenMutation::Insert { added, .. } |
|
||||
ChildrenMutation::Prepend { added, .. } => {
|
||||
self.children_count.set(
|
||||
self.children_count.get() + added.len() as u32);
|
||||
},
|
||||
ChildrenMutation::Replace { added, .. } => {
|
||||
self.children_count.set(
|
||||
self.children_count.get() - 1u32 + added.len() as u32);
|
||||
},
|
||||
ChildrenMutation::ReplaceAll { added, .. } => {
|
||||
self.children_count.set(added.len() as u32);
|
||||
},
|
||||
}
|
||||
if let Some(list) = self.child_list.get() {
|
||||
list.as_children_list().children_changed(mutation);
|
||||
}
|
||||
}
|
||||
|
||||
// This handles the ranges mentioned in steps 2-3 when removing a node.
|
||||
// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
self.super_type().unwrap().unbind_from_tree(context);
|
||||
self.ranges.drain_to_parent(context, self);
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of the changes that happened to a node.
|
||||
|
@ -2411,10 +2429,11 @@ impl<'a> ChildrenMutation<'a> {
|
|||
}
|
||||
|
||||
fn replace(prev: Option<&'a Node>,
|
||||
removed: &'a &'a Node,
|
||||
removed: &'a Option<&'a Node>,
|
||||
added: &'a [&'a Node],
|
||||
next: Option<&'a Node>)
|
||||
-> ChildrenMutation<'a> {
|
||||
if let Some(ref removed) = *removed {
|
||||
if let (None, None) = (prev, next) {
|
||||
ChildrenMutation::ReplaceAll {
|
||||
removed: ref_slice(removed),
|
||||
|
@ -2428,6 +2447,9 @@ impl<'a> ChildrenMutation<'a> {
|
|||
next: next,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ChildrenMutation::insert(prev, added, next)
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_all(removed: &'a [&'a Node], added: &'a [&'a Node])
|
||||
|
@ -2435,3 +2457,42 @@ impl<'a> ChildrenMutation<'a> {
|
|||
ChildrenMutation::ReplaceAll { removed: removed, added: added }
|
||||
}
|
||||
}
|
||||
|
||||
/// The context of the unbinding from a tree of a node when one of its
|
||||
/// inclusive ancestors is removed.
|
||||
pub struct UnbindContext<'a> {
|
||||
/// The index of the inclusive ancestor that was removed.
|
||||
index: Cell<Option<u32>>,
|
||||
/// The parent of the inclusive ancestor that was removed.
|
||||
pub parent: &'a Node,
|
||||
/// The previous sibling of the inclusive ancestor that was removed.
|
||||
prev_sibling: Option<&'a Node>,
|
||||
/// Whether the tree is in a document.
|
||||
pub tree_in_doc: bool,
|
||||
}
|
||||
|
||||
impl<'a> UnbindContext<'a> {
|
||||
/// Create a new `UnbindContext` value.
|
||||
fn new(parent: &'a Node,
|
||||
prev_sibling: Option<&'a Node>,
|
||||
cached_index: Option<u32>) -> Self {
|
||||
UnbindContext {
|
||||
index: Cell::new(cached_index),
|
||||
parent: parent,
|
||||
prev_sibling: prev_sibling,
|
||||
tree_in_doc: parent.is_in_doc(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The index of the inclusive ancestor that was removed from the tree.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn index(&self) -> u32 {
|
||||
if let Some(index) = self.index.get() {
|
||||
return index;
|
||||
}
|
||||
let index =
|
||||
self.prev_sibling.map(|sibling| sibling.index() + 1).unwrap_or(0);
|
||||
self.index.set(Some(index));
|
||||
index
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,17 @@ use dom::bindings::inheritance::Castable;
|
|||
use dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId};
|
||||
use dom::bindings::js::{JS, MutHeap, Root, RootedReference};
|
||||
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||
use dom::bindings::trace::RootedVec;
|
||||
use dom::bindings::trace::{JSTraceable, RootedVec};
|
||||
use dom::bindings::weakref::{WeakRef, WeakRefVec};
|
||||
use dom::characterdata::CharacterData;
|
||||
use dom::document::Document;
|
||||
use dom::documentfragment::DocumentFragment;
|
||||
use dom::node::Node;
|
||||
use dom::node::{Node, UnbindContext};
|
||||
use dom::text::Text;
|
||||
use std::cell::Cell;
|
||||
use js::jsapi::JSTracer;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use util::mem::HeapSizeOf;
|
||||
use util::str::DOMString;
|
||||
|
||||
#[dom_struct]
|
||||
|
@ -52,10 +55,15 @@ impl Range {
|
|||
start_container: &Node, start_offset: u32,
|
||||
end_container: &Node, end_offset: u32)
|
||||
-> Root<Range> {
|
||||
reflect_dom_object(box Range::new_inherited(start_container, start_offset,
|
||||
let range = reflect_dom_object(box Range::new_inherited(start_container, start_offset,
|
||||
end_container, end_offset),
|
||||
GlobalRef::Window(document.window()),
|
||||
RangeBinding::Wrap)
|
||||
RangeBinding::Wrap);
|
||||
start_container.ranges().push(WeakRef::new(&range));
|
||||
if start_container != end_container {
|
||||
end_container.ranges().push(WeakRef::new(&range));
|
||||
}
|
||||
range
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range
|
||||
|
@ -121,20 +129,32 @@ impl Range {
|
|||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-range-bp-set
|
||||
pub fn set_start(&self, node: &Node, offset: u32) {
|
||||
self.start.set(node, offset);
|
||||
if !(self.start <= self.end) {
|
||||
self.end.set(node, offset);
|
||||
fn set_start(&self, node: &Node, offset: u32) {
|
||||
if &self.start.node != node {
|
||||
if self.start.node == self.end.node {
|
||||
node.ranges().push(WeakRef::new(&self));
|
||||
} else if &self.end.node == node {
|
||||
self.StartContainer().ranges().remove(self);
|
||||
} else {
|
||||
node.ranges().push(self.StartContainer().ranges().remove(self));
|
||||
}
|
||||
}
|
||||
self.start.set(node, offset);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-range-bp-set
|
||||
pub fn set_end(&self, node: &Node, offset: u32) {
|
||||
self.end.set(node, offset);
|
||||
if !(self.end >= self.start) {
|
||||
self.start.set(node, offset);
|
||||
fn set_end(&self, node: &Node, offset: u32) {
|
||||
if &self.end.node != node {
|
||||
if self.end.node == self.start.node {
|
||||
node.ranges().push(WeakRef::new(&self));
|
||||
} else if &self.start.node == node {
|
||||
self.EndContainer().ranges().remove(self);
|
||||
} else {
|
||||
node.ranges().push(self.EndContainer().ranges().remove(self));
|
||||
}
|
||||
}
|
||||
self.end.set(node, offset);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset
|
||||
fn compare_point(&self, node: &Node, offset: u32) -> Fallible<Ordering> {
|
||||
|
@ -206,7 +226,7 @@ impl RangeMethods for Range {
|
|||
unreachable!();
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstartnode-offset
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstart
|
||||
fn SetStart(&self, node: &Node, offset: u32) -> ErrorResult {
|
||||
if node.is_doctype() {
|
||||
// Step 1.
|
||||
|
@ -215,13 +235,17 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
Err(Error::IndexSize)
|
||||
} else {
|
||||
// Step 3-4.
|
||||
// Step 3.
|
||||
self.set_start(node, offset);
|
||||
if !(self.start <= self.end) {
|
||||
// Step 4.
|
||||
self.set_end(node, offset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setendnode-offset
|
||||
// https://dom.spec.whatwg.org/#dom-range-setend
|
||||
fn SetEnd(&self, node: &Node, offset: u32) -> ErrorResult {
|
||||
if node.is_doctype() {
|
||||
// Step 1.
|
||||
|
@ -230,59 +254,63 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
Err(Error::IndexSize)
|
||||
} else {
|
||||
// Step 3-4.
|
||||
// Step 3.
|
||||
self.set_end(node, offset);
|
||||
if !(self.end >= self.start) {
|
||||
// Step 4.
|
||||
self.set_start(node, offset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstartbeforenode
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstartbefore
|
||||
fn SetStartBefore(&self, node: &Node) -> ErrorResult {
|
||||
let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType));
|
||||
self.SetStart(parent.r(), node.index())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstartafternode
|
||||
// https://dom.spec.whatwg.org/#dom-range-setstartafter
|
||||
fn SetStartAfter(&self, node: &Node) -> ErrorResult {
|
||||
let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType));
|
||||
self.SetStart(parent.r(), node.index() + 1)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setendbeforenode
|
||||
// https://dom.spec.whatwg.org/#dom-range-setendbefore
|
||||
fn SetEndBefore(&self, node: &Node) -> ErrorResult {
|
||||
let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType));
|
||||
self.SetEnd(parent.r(), node.index())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-setendafternode
|
||||
// https://dom.spec.whatwg.org/#dom-range-setendafter
|
||||
fn SetEndAfter(&self, node: &Node) -> ErrorResult {
|
||||
let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType));
|
||||
self.SetEnd(parent.r(), node.index() + 1)
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-collapsetostart
|
||||
// https://dom.spec.whatwg.org/#dom-range-collapse
|
||||
fn Collapse(&self, to_start: bool) {
|
||||
if to_start {
|
||||
self.end.set(&self.StartContainer(), self.StartOffset());
|
||||
self.set_end(&self.StartContainer(), self.StartOffset());
|
||||
} else {
|
||||
self.start.set(&self.EndContainer(), self.EndOffset());
|
||||
self.set_start(&self.EndContainer(), self.EndOffset());
|
||||
}
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-selectnodenode
|
||||
// https://dom.spec.whatwg.org/#dom-range-selectnode
|
||||
fn SelectNode(&self, node: &Node) -> ErrorResult {
|
||||
// Steps 1, 2.
|
||||
let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType));
|
||||
// Step 3.
|
||||
let index = node.index();
|
||||
// Step 4.
|
||||
self.start.set(&parent, index);
|
||||
self.set_start(&parent, index);
|
||||
// Step 5.
|
||||
self.end.set(&parent, index + 1);
|
||||
self.set_end(&parent, index + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode
|
||||
// https://dom.spec.whatwg.org/#dom-range-selectnodecontents
|
||||
fn SelectNodeContents(&self, node: &Node) -> ErrorResult {
|
||||
if node.is_doctype() {
|
||||
// Step 1.
|
||||
|
@ -291,13 +319,13 @@ impl RangeMethods for Range {
|
|||
// Step 2.
|
||||
let length = node.len();
|
||||
// Step 3.
|
||||
self.start.set(node, 0);
|
||||
self.set_start(node, 0);
|
||||
// Step 4.
|
||||
self.end.set(node, length);
|
||||
self.set_end(node, length);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-compareboundarypointshow-sourcerange
|
||||
// https://dom.spec.whatwg.org/#dom-range-compareboundarypoints
|
||||
fn CompareBoundaryPoints(&self, how: u16, other: &Range)
|
||||
-> Fallible<i16> {
|
||||
if how > RangeConstants::END_TO_START {
|
||||
|
@ -342,7 +370,7 @@ impl RangeMethods for Range {
|
|||
&self.EndContainer(), self.EndOffset())
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-ispointinrangenode-offset
|
||||
// https://dom.spec.whatwg.org/#dom-range-ispointinrange
|
||||
fn IsPointInRange(&self, node: &Node, offset: u32) -> Fallible<bool> {
|
||||
match self.compare_point(node, offset) {
|
||||
Ok(Ordering::Less) => Ok(false),
|
||||
|
@ -356,7 +384,7 @@ impl RangeMethods for Range {
|
|||
}
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset
|
||||
// https://dom.spec.whatwg.org/#dom-range-comparepoint
|
||||
fn ComparePoint(&self, node: &Node, offset: u32) -> Fallible<i16> {
|
||||
self.compare_point(node, offset).map(|order| {
|
||||
match order {
|
||||
|
@ -410,12 +438,10 @@ impl RangeMethods for Range {
|
|||
}
|
||||
|
||||
if end_node == start_node {
|
||||
if let Some(text) = start_node.downcast::<CharacterData>() {
|
||||
// Step 4.1.
|
||||
let clone = start_node.CloneNode(true);
|
||||
// Step 4.2.
|
||||
let text = text.SubstringData(start_offset, end_offset - start_offset);
|
||||
clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap());
|
||||
if let Some(cdata) = start_node.downcast::<CharacterData>() {
|
||||
// Steps 4.1-2.
|
||||
let data = cdata.SubstringData(start_offset, end_offset - start_offset).unwrap();
|
||||
let clone = cdata.clone_with_data(data, &start_node.owner_doc());
|
||||
// Step 4.3.
|
||||
try!(fragment.upcast::<Node>().AppendChild(&clone));
|
||||
// Step 4.4
|
||||
|
@ -429,13 +455,11 @@ impl RangeMethods for Range {
|
|||
|
||||
if let Some(child) = first_contained_child {
|
||||
// Step 13.
|
||||
if let Some(text) = child.downcast::<CharacterData>() {
|
||||
if let Some(cdata) = child.downcast::<CharacterData>() {
|
||||
assert!(child == start_node);
|
||||
// Step 13.1.
|
||||
let clone = start_node.CloneNode(true); // CharacterData has no children.
|
||||
// Step 13.2
|
||||
let text = text.SubstringData(start_offset, start_node.len() - start_offset);
|
||||
clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap());
|
||||
// Steps 13.1-2.
|
||||
let data = cdata.SubstringData(start_offset, start_node.len() - start_offset).unwrap();
|
||||
let clone = cdata.clone_with_data(data, &start_node.owner_doc());
|
||||
// Step 13.3.
|
||||
try!(fragment.upcast::<Node>().AppendChild(&clone));
|
||||
} else {
|
||||
|
@ -466,13 +490,11 @@ impl RangeMethods for Range {
|
|||
|
||||
if let Some(child) = last_contained_child {
|
||||
// Step 16.
|
||||
if let Some(text) = child.downcast::<CharacterData>() {
|
||||
if let Some(cdata) = child.downcast::<CharacterData>() {
|
||||
assert!(child == end_node);
|
||||
// Step 16.1.
|
||||
let clone = end_node.CloneNode(true); // CharacterData has no children.
|
||||
// Step 16.2.
|
||||
let text = text.SubstringData(0, end_offset);
|
||||
clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap());
|
||||
// Steps 16.1-2.
|
||||
let data = cdata.SubstringData(0, end_offset).unwrap();
|
||||
let clone = cdata.clone_with_data(data, &start_node.owner_doc());
|
||||
// Step 16.3.
|
||||
try!(fragment.upcast::<Node>().AppendChild(&clone));
|
||||
} else {
|
||||
|
@ -839,10 +861,12 @@ impl BoundaryPoint {
|
|||
}
|
||||
}
|
||||
|
||||
fn set(&self, node: &Node, offset: u32) {
|
||||
debug_assert!(!node.is_doctype());
|
||||
debug_assert!(offset <= node.len());
|
||||
pub fn set(&self, node: &Node, offset: u32) {
|
||||
self.node.set(node);
|
||||
self.set_offset(offset);
|
||||
}
|
||||
|
||||
pub fn set_offset(&self, offset: u32) {
|
||||
self.offset.set(offset);
|
||||
}
|
||||
}
|
||||
|
@ -900,3 +924,259 @@ fn bp_position(a_node: &Node, a_offset: u32,
|
|||
Some(Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WeakRangeVec {
|
||||
cell: UnsafeCell<WeakRefVec<Range>>,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
impl WeakRangeVec {
|
||||
/// Create a new vector of weak references.
|
||||
pub fn new() -> Self {
|
||||
WeakRangeVec { cell: UnsafeCell::new(WeakRefVec::new()) }
|
||||
}
|
||||
|
||||
/// Whether that vector of ranges is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
unsafe { (*self.cell.get()).is_empty() }
|
||||
}
|
||||
|
||||
/// Used for steps 2.1-2. when inserting a node.
|
||||
/// https://dom.spec.whatwg.org/#concept-node-insert
|
||||
pub fn increase_above(&self, node: &Node, offset: u32, delta: u32) {
|
||||
self.map_offset_above(node, offset, |offset| offset + delta);
|
||||
}
|
||||
|
||||
/// Used for steps 4-5. when removing a node.
|
||||
/// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
pub fn decrease_above(&self, node: &Node, offset: u32, delta: u32) {
|
||||
self.map_offset_above(node, offset, |offset| offset - delta);
|
||||
}
|
||||
|
||||
/// Used for steps 2-3. when removing a node.
|
||||
/// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
pub fn drain_to_parent(&self, context: &UnbindContext, child: &Node) {
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let offset = context.index();
|
||||
let parent = context.parent;
|
||||
unsafe {
|
||||
let mut ranges = &mut *self.cell.get();
|
||||
|
||||
ranges.update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
if &range.start.node == parent || &range.end.node == parent {
|
||||
entry.remove();
|
||||
}
|
||||
if &range.start.node == child {
|
||||
range.start.set(context.parent, offset);
|
||||
}
|
||||
if &range.end.node == child {
|
||||
range.end.set(context.parent, offset);
|
||||
}
|
||||
});
|
||||
|
||||
(*context.parent.ranges().cell.get()).extend(ranges.drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for steps 7.1-2. when normalizing a node.
|
||||
/// https://dom.spec.whatwg.org/#dom-node-normalize
|
||||
pub fn drain_to_preceding_text_sibling(&self, node: &Node, sibling: &Node, length: u32) {
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut ranges = &mut *self.cell.get();
|
||||
|
||||
ranges.update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
if &range.start.node == sibling || &range.end.node == sibling {
|
||||
entry.remove();
|
||||
}
|
||||
if &range.start.node == node {
|
||||
range.start.set(sibling, range.StartOffset() + length);
|
||||
}
|
||||
if &range.end.node == node {
|
||||
range.end.set(sibling, range.EndOffset() + length);
|
||||
}
|
||||
});
|
||||
|
||||
(*sibling.ranges().cell.get()).extend(ranges.drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for steps 7.3-4. when normalizing a node.
|
||||
/// https://dom.spec.whatwg.org/#dom-node-normalize
|
||||
pub fn move_to_text_child_at(&self,
|
||||
node: &Node, offset: u32,
|
||||
child: &Node, new_offset: u32) {
|
||||
unsafe {
|
||||
let child_ranges = &mut *child.ranges().cell.get();
|
||||
|
||||
(*self.cell.get()).update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
|
||||
let node_is_start = &range.start.node == node;
|
||||
let node_is_end = &range.end.node == node;
|
||||
|
||||
let move_start = node_is_start && range.StartOffset() == offset;
|
||||
let move_end = node_is_end && range.EndOffset() == offset;
|
||||
|
||||
let remove_from_node = move_start && move_end ||
|
||||
move_start && !node_is_end ||
|
||||
move_end && !node_is_start;
|
||||
|
||||
let already_in_child = &range.start.node == child || &range.end.node == child;
|
||||
let push_to_child = !already_in_child && (move_start || move_end);
|
||||
|
||||
if remove_from_node {
|
||||
let ref_ = entry.remove();
|
||||
if push_to_child {
|
||||
child_ranges.push(ref_);
|
||||
}
|
||||
} else if push_to_child {
|
||||
child_ranges.push(WeakRef::new(&range));
|
||||
}
|
||||
|
||||
if move_start {
|
||||
range.start.set(child, new_offset);
|
||||
}
|
||||
if move_end {
|
||||
range.end.set(child, new_offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for steps 8-11. when replacing character data.
|
||||
/// https://dom.spec.whatwg.org/#concept-cd-replace
|
||||
pub fn replace_code_units(&self,
|
||||
node: &Node, offset: u32,
|
||||
removed_code_units: u32, added_code_units: u32) {
|
||||
self.map_offset_above(node, offset, |range_offset| {
|
||||
if range_offset <= offset + removed_code_units {
|
||||
offset
|
||||
} else {
|
||||
range_offset + added_code_units - removed_code_units
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Used for steps 7.2-3. when splitting a text node.
|
||||
/// https://dom.spec.whatwg.org/#concept-text-split
|
||||
pub fn move_to_following_text_sibling_above(&self,
|
||||
node: &Node, offset: u32,
|
||||
sibling: &Node) {
|
||||
unsafe {
|
||||
let sibling_ranges = &mut *sibling.ranges().cell.get();
|
||||
|
||||
(*self.cell.get()).update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
let start_offset = range.StartOffset();
|
||||
let end_offset = range.EndOffset();
|
||||
|
||||
let node_is_start = &range.start.node == node;
|
||||
let node_is_end = &range.end.node == node;
|
||||
|
||||
let move_start = node_is_start && start_offset > offset;
|
||||
let move_end = node_is_end && end_offset > offset;
|
||||
|
||||
let remove_from_node = move_start && move_end ||
|
||||
move_start && !node_is_end ||
|
||||
move_end && !node_is_start;
|
||||
|
||||
let already_in_sibling =
|
||||
&range.start.node == sibling || &range.end.node == sibling;
|
||||
let push_to_sibling = !already_in_sibling && (move_start || move_end);
|
||||
|
||||
if remove_from_node {
|
||||
let ref_ = entry.remove();
|
||||
if push_to_sibling {
|
||||
sibling_ranges.push(ref_);
|
||||
}
|
||||
} else if push_to_sibling {
|
||||
sibling_ranges.push(WeakRef::new(&range));
|
||||
}
|
||||
|
||||
if move_start {
|
||||
range.start.set(sibling, start_offset - offset);
|
||||
}
|
||||
if move_end {
|
||||
range.end.set(sibling, end_offset - offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for steps 7.4-5. when splitting a text node.
|
||||
/// https://dom.spec.whatwg.org/#concept-text-split
|
||||
pub fn increment_at(&self, node: &Node, offset: u32) {
|
||||
unsafe {
|
||||
(*self.cell.get()).update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
if &range.start.node == node && offset == range.StartOffset() {
|
||||
range.start.set_offset(offset + 1);
|
||||
}
|
||||
if &range.end.node == node && offset == range.EndOffset() {
|
||||
range.end.set_offset(offset + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for steps 9.1-2. when splitting a text node.
|
||||
/// https://dom.spec.whatwg.org/#concept-text-split
|
||||
pub fn clamp_above(&self, node: &Node, offset: u32) {
|
||||
self.map_offset_above(node, offset, |_| offset);
|
||||
}
|
||||
|
||||
fn map_offset_above<F: FnMut(u32) -> u32>(&self, node: &Node, offset: u32, mut f: F) {
|
||||
unsafe {
|
||||
(*self.cell.get()).update(|entry| {
|
||||
let range = entry.root().unwrap();
|
||||
let start_offset = range.StartOffset();
|
||||
if &range.start.node == node && start_offset > offset {
|
||||
range.start.set_offset(f(start_offset));
|
||||
}
|
||||
let end_offset = range.EndOffset();
|
||||
if &range.end.node == node && end_offset > offset {
|
||||
range.end.set_offset(f(end_offset));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&self, ref_: WeakRef<Range>) {
|
||||
unsafe {
|
||||
(*self.cell.get()).push(ref_);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, range: &Range) -> WeakRef<Range> {
|
||||
unsafe {
|
||||
let ranges = &mut *self.cell.get();
|
||||
let position = ranges.iter().position(|ref_| {
|
||||
ref_ == range
|
||||
}).unwrap();
|
||||
ranges.swap_remove(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
impl HeapSizeOf for WeakRangeVec {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
unsafe { (*self.cell.get()).heap_size_of_children() }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
impl JSTraceable for WeakRangeVec {
|
||||
fn trace(&self, _: *mut JSTracer) {
|
||||
unsafe { (*self.cell.get()).retain_alive() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,15 +62,18 @@ impl TextMethods for Text {
|
|||
// Step 6.
|
||||
let parent = node.GetParentNode();
|
||||
if let Some(ref parent) = parent {
|
||||
// Step 7.
|
||||
// Step 7.1.
|
||||
parent.InsertBefore(new_node.upcast(), node.GetNextSibling().r()).unwrap();
|
||||
// TODO: Ranges.
|
||||
// Steps 7.2-3.
|
||||
node.ranges().move_to_following_text_sibling_above(node, offset, new_node.upcast());
|
||||
// Steps 7.4-5.
|
||||
parent.ranges().increment_at(&parent, node.index() + 1);
|
||||
}
|
||||
// Step 8.
|
||||
cdata.DeleteData(offset, count).unwrap();
|
||||
if parent.is_none() {
|
||||
// Step 9.
|
||||
// TODO: Ranges
|
||||
node.ranges().clamp_above(&node, offset);
|
||||
}
|
||||
// Step 10.
|
||||
Ok(new_node)
|
||||
|
|
|
@ -42,7 +42,7 @@ use dom::htmltablesectionelement::HTMLTableSectionElement;
|
|||
use dom::htmltemplateelement::HTMLTemplateElement;
|
||||
use dom::htmltextareaelement::HTMLTextAreaElement;
|
||||
use dom::htmltitleelement::HTMLTitleElement;
|
||||
use dom::node::{ChildrenMutation, CloneChildrenFlag, Node};
|
||||
use dom::node::{ChildrenMutation, CloneChildrenFlag, Node, UnbindContext};
|
||||
use string_cache::Atom;
|
||||
use util::str::DOMString;
|
||||
|
||||
|
@ -82,9 +82,9 @@ pub trait VirtualMethods {
|
|||
|
||||
/// Called when a Node is removed from a tree, where 'tree_in_doc'
|
||||
/// indicates whether the tree is part of a Document.
|
||||
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
||||
fn unbind_from_tree(&self, context: &UnbindContext) {
|
||||
if let Some(ref s) = self.super_type() {
|
||||
s.unbind_from_tree(tree_in_doc);
|
||||
s.unbind_from_tree(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#![feature(custom_derive)]
|
||||
#![feature(fnbox)]
|
||||
#![feature(hashmap_hasher)]
|
||||
#![feature(iter_arith)]
|
||||
#![feature(mpsc_select)]
|
||||
#![feature(nonzero)]
|
||||
#![feature(on_unimplemented)]
|
||||
|
|
|
@ -38,10 +38,10 @@ macro_rules! sizeof_checker (
|
|||
|
||||
// Update the sizes here
|
||||
sizeof_checker!(size_event_target, EventTarget, 40);
|
||||
sizeof_checker!(size_node, Node, 160);
|
||||
sizeof_checker!(size_element, Element, 304);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 320);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 320);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 320);
|
||||
sizeof_checker!(size_text, Text, 192);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 192);
|
||||
sizeof_checker!(size_node, Node, 184);
|
||||
sizeof_checker!(size_element, Element, 328);
|
||||
sizeof_checker!(size_htmlelement, HTMLElement, 344);
|
||||
sizeof_checker!(size_div, HTMLDivElement, 344);
|
||||
sizeof_checker!(size_span, HTMLSpanElement, 344);
|
||||
sizeof_checker!(size_text, Text, 216);
|
||||
sizeof_checker!(size_characterdata, CharacterData, 216);
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
[Range-deleteContents.html]
|
||||
type: testharness
|
||||
[Resulting cursor position for range 1 [paras[0\].firstChild, 0, paras[0\].firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 2 [paras[0\].firstChild, 2, paras[0\].firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 3 [paras[0\].firstChild, 2, paras[0\].firstChild, 9\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 5 [paras[1\].firstChild, 2, paras[1\].firstChild, 9\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 7 [detachedPara1.firstChild, 2, detachedPara1.firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 9 [foreignPara1.firstChild, 2, foreignPara1.firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -30,27 +12,6 @@
|
|||
[Resulting DOM for range 24 [document, 0, document, 2\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 25 [comment, 2, comment, 3\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 30 [detachedTextNode, 0, detachedTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 31 [detachedForeignTextNode, 0, detachedForeignTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 32 [detachedXmlTextNode, 0, detachedXmlTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 33 [detachedComment, 3, detachedComment, 4\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 34 [detachedForeignComment, 0, detachedForeignComment, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 35 [detachedXmlComment, 2, detachedXmlComment, 6\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting DOM for range 10 [document.documentElement, 0, document.documentElement, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -66,21 +27,6 @@
|
|||
[Resulting DOM for range 27 [foreignDoc, 1, foreignComment, 2\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 37 [processingInstruction, 0, processingInstruction, 4\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 38 [paras[1\].firstChild, 0, paras[1\].firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 39 [paras[1\].firstChild, 2, paras[1\].firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 40 [detachedPara1.firstChild, 0, detachedPara1.firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 41 [foreignPara1.firstChild, 0, foreignPara1.firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting DOM for range 49 [document, 1, document, 2\]]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
[Range-extractContents.html]
|
||||
type: testharness
|
||||
[Resulting cursor position for range 1 [paras[0\].firstChild, 0, paras[0\].firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 2 [paras[0\].firstChild, 2, paras[0\].firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 3 [paras[0\].firstChild, 2, paras[0\].firstChild, 9\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 5 [paras[1\].firstChild, 2, paras[1\].firstChild, 9\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 7 [detachedPara1.firstChild, 2, detachedPara1.firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 9 [foreignPara1.firstChild, 2, foreignPara1.firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 18 [paras[0\].firstChild, 0, paras[1\].firstChild, 0\]]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -27,42 +9,6 @@
|
|||
[Resulting cursor position for range 20 [paras[0\].firstChild, 3, paras[3\], 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 25 [comment, 2, comment, 3\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 30 [detachedTextNode, 0, detachedTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 31 [detachedForeignTextNode, 0, detachedForeignTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 32 [detachedXmlTextNode, 0, detachedXmlTextNode, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 33 [detachedComment, 3, detachedComment, 4\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 34 [detachedForeignComment, 0, detachedForeignComment, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 35 [detachedXmlComment, 2, detachedXmlComment, 6\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 37 [processingInstruction, 0, processingInstruction, 4\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 38 [paras[1\].firstChild, 0, paras[1\].firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 39 [paras[1\].firstChild, 2, paras[1\].firstChild, 8\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 40 [detachedPara1.firstChild, 0, detachedPara1.firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 41 [foreignPara1.firstChild, 0, foreignPara1.firstChild, 1\]]
|
||||
expected: FAIL
|
||||
|
||||
[Resulting cursor position for range 50 [paras[2\].firstChild, 4, comment, 2\]]
|
||||
expected: FAIL
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue