@@ -468,7 +468,7 @@ Node.prototype = {
468
468
return _insertBefore ( this , newChild , refChild ) ;
469
469
} ,
470
470
replaceChild :function ( newChild , oldChild ) { //raises
471
- this . insertBefore ( newChild , oldChild ) ;
471
+ _insertBefore ( this , newChild , oldChild , assertPreReplacementValidityInDocument ) ;
472
472
if ( oldChild ) {
473
473
this . removeChild ( oldChild ) ;
474
474
}
@@ -590,6 +590,7 @@ function _visitNode(node,callback){
590
590
591
591
592
592
function Document ( ) {
593
+ this . ownerDocument = this ;
593
594
}
594
595
595
596
function _onAddAttribute ( doc , el , newAttr ) {
@@ -747,64 +748,200 @@ function isElementInsertionPossible(doc, child) {
747
748
var docTypeNode = find ( parentChildNodes , isDocTypeNode ) ;
748
749
return ! ( child && docTypeNode && parentChildNodes . indexOf ( docTypeNode ) > parentChildNodes . indexOf ( child ) ) ;
749
750
}
751
+
750
752
/**
753
+ * Check if en element node can be inserted before `child`, or at the end if child is falsy,
754
+ * according to the presence and position of a doctype node on the same level.
755
+ *
756
+ * @param {Node } doc The document node
757
+ * @param {Node } child the node that would become the nextSibling if the element would be inserted
758
+ * @returns {boolean } `true` if an element can be inserted before child
751
759
* @private
760
+ * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
761
+ */
762
+ function isElementReplacementPossible ( doc , child ) {
763
+ var parentChildNodes = doc . childNodes || [ ] ;
764
+
765
+ function hasElementChildThatIsNotChild ( node ) {
766
+ return isElementNode ( node ) && node !== child ;
767
+ }
768
+
769
+ if ( find ( parentChildNodes , hasElementChildThatIsNotChild ) ) {
770
+ return false ;
771
+ }
772
+ var docTypeNode = find ( parentChildNodes , isDocTypeNode ) ;
773
+ return ! ( child && docTypeNode && parentChildNodes . indexOf ( docTypeNode ) > parentChildNodes . indexOf ( child ) ) ;
774
+ }
775
+
776
+ /**
777
+ * @private
778
+ * Steps 1-5 of the checks before inserting and before replacing a child are the same.
779
+ *
752
780
* @param {Node } parent the parent node to insert `node` into
753
781
* @param {Node } node the node to insert
754
782
* @param {Node= } child the node that should become the `nextSibling` of `node`
755
783
* @returns {Node }
756
784
* @throws DOMException for several node combinations that would create a DOM that is not well-formed.
757
785
* @throws DOMException if `child` is provided but is not a child of `parent`.
758
786
* @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
787
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
759
788
*/
760
- function _insertBefore ( parent , node , child ) {
789
+ function assertPreInsertionValidity1to5 ( parent , node , child ) {
790
+ // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
761
791
if ( ! hasValidParentNodeType ( parent ) ) {
762
792
throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Unexpected parent node type ' + parent . nodeType ) ;
763
793
}
794
+ // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException.
795
+ // not implemented!
796
+ // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException.
764
797
if ( child && child . parentNode !== parent ) {
765
798
throw new DOMException ( NOT_FOUND_ERR , 'child not in parent' ) ;
766
799
}
767
800
if (
801
+ // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
768
802
! hasInsertableNodeType ( node ) ||
803
+ // 5. If either `node` is a Text node and `parent` is a document,
769
804
// the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
770
805
// || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
806
+ // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException.
771
807
( isDocTypeNode ( node ) && parent . nodeType !== Node . DOCUMENT_NODE )
772
808
) {
773
809
throw new DOMException (
774
810
HIERARCHY_REQUEST_ERR ,
775
811
'Unexpected node type ' + node . nodeType + ' for parent node type ' + parent . nodeType
776
812
) ;
777
813
}
814
+ }
815
+
816
+ /**
817
+ * @private
818
+ * Step 6 of the checks before inserting and before replacing a child are different.
819
+ *
820
+ * @param {Document } parent the parent node to insert `node` into
821
+ * @param {Node } node the node to insert
822
+ * @param {Node | undefined } child the node that should become the `nextSibling` of `node`
823
+ * @returns {Node }
824
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
825
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
826
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
827
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
828
+ */
829
+ function assertPreInsertionValidityInDocument ( parent , node , child ) {
778
830
var parentChildNodes = parent . childNodes || [ ] ;
779
831
var nodeChildNodes = node . childNodes || [ ] ;
780
- if ( parent . nodeType === Node . DOCUMENT_NODE ) {
781
- if ( node . nodeType === Node . DOCUMENT_FRAGMENT_NODE ) {
782
- var nodeChildElements = nodeChildNodes . filter ( isElementNode ) ;
783
- if ( nodeChildElements . length > 1 || find ( nodeChildNodes , isTextNode ) ) {
784
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'More than one element or text in fragment' ) ;
785
- }
786
- if ( nodeChildElements . length === 1 && ! isElementInsertionPossible ( parent , child ) ) {
787
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Element in fragment can not be inserted before doctype' ) ;
788
- }
832
+
833
+ // DocumentFragment
834
+ if ( node . nodeType === Node . DOCUMENT_FRAGMENT_NODE ) {
835
+ var nodeChildElements = nodeChildNodes . filter ( isElementNode ) ;
836
+ // If node has more than one element child or has a Text node child.
837
+ if ( nodeChildElements . length > 1 || find ( nodeChildNodes , isTextNode ) ) {
838
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'More than one element or text in fragment' ) ;
789
839
}
790
- if ( isElementNode ( node ) ) {
791
- if ( find ( parentChildNodes , isElementNode ) || ! isElementInsertionPossible ( parent , child ) ) {
792
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one element can be added and only after doctype' ) ;
793
- }
840
+ // Otherwise, if ` node` has one element child and either `parent` has an element child,
841
+ // `child` is a doctype, or ` child` is non-null and a doctype is following `child`.
842
+ if ( nodeChildElements . length === 1 && ! isElementInsertionPossible ( parent , child ) ) {
843
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Element in fragment can not be inserted before doctype' ) ;
794
844
}
795
- if ( isDocTypeNode ( node ) ) {
796
- if ( find ( parentChildNodes , isDocTypeNode ) ) {
797
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one doctype is allowed' ) ;
798
- }
799
- var parentElementChild = find ( parentChildNodes , isElementNode ) ;
800
- if ( child && parentChildNodes . indexOf ( parentElementChild ) < parentChildNodes . indexOf ( child ) ) {
801
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Doctype can only be inserted before an element' ) ;
802
- }
803
- if ( ! child && parentElementChild ) {
804
- throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Doctype can not be appended since element is present' ) ;
805
- }
845
+ }
846
+ // Element
847
+ if ( isElementNode ( node ) ) {
848
+ // `parent` has an element child, `child` is a doctype,
849
+ // or `child` is non-null and a doctype is following `child`.
850
+ if ( ! isElementInsertionPossible ( parent , child ) ) {
851
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one element can be added and only after doctype' ) ;
852
+ }
853
+ }
854
+ // DocumentType
855
+ if ( isDocTypeNode ( node ) ) {
856
+ // `parent` has a doctype child,
857
+ if ( find ( parentChildNodes , isDocTypeNode ) ) {
858
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one doctype is allowed' ) ;
859
+ }
860
+ var parentElementChild = find ( parentChildNodes , isElementNode ) ;
861
+ // `child` is non-null and an element is preceding `child`,
862
+ if ( child && parentChildNodes . indexOf ( parentElementChild ) < parentChildNodes . indexOf ( child ) ) {
863
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Doctype can only be inserted before an element' ) ;
864
+ }
865
+ // or `child` is null and `parent` has an element child.
866
+ if ( ! child && parentElementChild ) {
867
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Doctype can not be appended since element is present' ) ;
806
868
}
807
869
}
870
+ }
871
+
872
+ /**
873
+ * @private
874
+ * Step 6 of the checks before inserting and before replacing a child are different.
875
+ *
876
+ * @param {Document } parent the parent node to insert `node` into
877
+ * @param {Node } node the node to insert
878
+ * @param {Node | undefined } child the node that should become the `nextSibling` of `node`
879
+ * @returns {Node }
880
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
881
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
882
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
883
+ * @see https://dom.spec.whatwg.org/#concept-node-replace
884
+ */
885
+ function assertPreReplacementValidityInDocument ( parent , node , child ) {
886
+ var parentChildNodes = parent . childNodes || [ ] ;
887
+ var nodeChildNodes = node . childNodes || [ ] ;
888
+
889
+ // DocumentFragment
890
+ if ( node . nodeType === Node . DOCUMENT_FRAGMENT_NODE ) {
891
+ var nodeChildElements = nodeChildNodes . filter ( isElementNode ) ;
892
+ // If `node` has more than one element child or has a Text node child.
893
+ if ( nodeChildElements . length > 1 || find ( nodeChildNodes , isTextNode ) ) {
894
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'More than one element or text in fragment' ) ;
895
+ }
896
+ // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`.
897
+ if ( nodeChildElements . length === 1 && ! isElementReplacementPossible ( parent , child ) ) {
898
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Element in fragment can not be inserted before doctype' ) ;
899
+ }
900
+ }
901
+ // Element
902
+ if ( isElementNode ( node ) ) {
903
+ // `parent` has an element child that is not `child` or a doctype is following `child`.
904
+ if ( ! isElementReplacementPossible ( parent , child ) ) {
905
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one element can be added and only after doctype' ) ;
906
+ }
907
+ }
908
+ // DocumentType
909
+ if ( isDocTypeNode ( node ) ) {
910
+ function hasDoctypeChildThatIsNotChild ( node ) {
911
+ return isDocTypeNode ( node ) && node !== child ;
912
+ }
913
+
914
+ // `parent` has a doctype child that is not `child`,
915
+ if ( find ( parentChildNodes , hasDoctypeChildThatIsNotChild ) ) {
916
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Only one doctype is allowed' ) ;
917
+ }
918
+ var parentElementChild = find ( parentChildNodes , isElementNode ) ;
919
+ // or an element is preceding `child`.
920
+ if ( child && parentChildNodes . indexOf ( parentElementChild ) < parentChildNodes . indexOf ( child ) ) {
921
+ throw new DOMException ( HIERARCHY_REQUEST_ERR , 'Doctype can only be inserted before an element' ) ;
922
+ }
923
+ }
924
+ }
925
+
926
+ /**
927
+ * @private
928
+ * @param {Node } parent the parent node to insert `node` into
929
+ * @param {Node } node the node to insert
930
+ * @param {Node= } child the node that should become the `nextSibling` of `node`
931
+ * @returns {Node }
932
+ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
933
+ * @throws DOMException if `child` is provided but is not a child of `parent`.
934
+ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
935
+ */
936
+ function _insertBefore ( parent , node , child , _inDocumentAssertion ) {
937
+ // To ensure pre-insertion validity of a node into a parent before a child, run these steps:
938
+ assertPreInsertionValidity1to5 ( parent , node , child ) ;
939
+
940
+ // If parent is a document, and any of the statements below, switched on the interface node implements,
941
+ // are true, then throw a "HierarchyRequestError" DOMException.
942
+ if ( parent . nodeType === Node . DOCUMENT_NODE ) {
943
+ ( _inDocumentAssertion || assertPreInsertionValidityInDocument ) ( parent , node , child ) ;
944
+ }
808
945
809
946
var cp = node . parentNode ;
810
947
if ( cp ) {
@@ -912,6 +1049,17 @@ Document.prototype = {
912
1049
}
913
1050
return _removeChild ( this , oldChild ) ;
914
1051
} ,
1052
+ replaceChild : function ( newChild , oldChild ) {
1053
+ //raises
1054
+ _insertBefore ( this , newChild , oldChild , assertPreReplacementValidityInDocument ) ;
1055
+ newChild . ownerDocument = this ;
1056
+ if ( oldChild ) {
1057
+ this . removeChild ( oldChild ) ;
1058
+ }
1059
+ if ( isElementNode ( newChild ) ) {
1060
+ this . documentElement = newChild ;
1061
+ }
1062
+ } ,
915
1063
// Introduced in DOM Level 2:
916
1064
importNode : function ( importedNode , deep ) {
917
1065
return importNode ( this , importedNode , deep ) ;
0 commit comments