浏览代码

Merge pull request #86 from ckeditor/t/85

Added: dom.getCommonAncestor, dom.getParents.
Piotrek Koszuliński 9 年之前
父节点
当前提交
ce7e866927

+ 27 - 0
packages/ckeditor5-utils/src/dom/getancestors.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals Node */
+
+/**
+ * Returns all ancestors of given DOM node, starting from the top-most (root). Includes the given node itself. If the
+ * node is a part of `DocumentFragment` that `DocumentFragment` will be returned. In contrary, if the node is
+ * appended to a `Document`, that `Document` will not be returned (algorithms operating on DOM tree care for `Document#documentElement`
+ * at most, which will be returned).
+ *
+ * @param {Node} node DOM node.
+ * @returns {Array.<Node|DocumentFragment>} Array of given `node` parents.
+ */
+export default function getAncestors( node ) {
+	const nodes = [];
+
+	// We are interested in `Node`s `DocumentFragment`s only.
+	while ( node && node.nodeType != Node.DOCUMENT_NODE ) {
+		nodes.unshift( node );
+		node = node.parentNode;
+	}
+
+	return nodes;
+}

+ 27 - 0
packages/ckeditor5-utils/src/dom/getcommonancestor.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import getAncestors from './getancestors.js';
+
+/**
+ * Searches and returns the lowest common ancestor of two given nodes.
+ *
+ * @param {Node} nodeA First node.
+ * @param {Node} nodeB Second node.
+ * @returns {Node|DocumentFragment|Document|null} Lowest common ancestor of both nodes or `null` if nodes do not have a common ancestor.
+ */
+export default function getCommonAncestor( nodeA, nodeB ) {
+	const ancestorsA = getAncestors( nodeA );
+	const ancestorsB = getAncestors( nodeB );
+
+	let i = 0;
+
+	// It does not matter which array is shorter.
+	while ( ancestorsA[ i ] == ancestorsB[ i ] && ancestorsA[ i ] ) {
+		i++;
+	}
+
+	return i === 0 ? null : ancestorsA[ i - 1 ];
+}

+ 54 - 0
packages/ckeditor5-utils/tests/dom/getancestors.js

@@ -0,0 +1,54 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+/* bender-tags: dom, browser-only */
+
+import getAncestors from '/ckeditor5/utils/dom/getancestors.js';
+import createElement from '/ckeditor5/utils/dom/createelement.js';
+
+describe( 'getAncestors', () => {
+	it( 'should return all parents of given node and the node itself, starting from top-most parent', () => {
+		// DIV
+		//  |- P (1)
+		//  |  |- SPAN (1)
+		//  |     |- B
+		//  |
+		//  |- P (2)
+		//     |- I
+		const b = createElement( document, 'b' );
+		const span = createElement( document, 'span', {}, [ b ] );
+		const p1 = createElement( document, 'p', {}, [ span ] );
+		const p2 = createElement( document, 'p', {}, [ createElement( document, 'i' ) ] );
+		const div = createElement( document, 'div', {}, [ p1, p2 ] );
+
+		expect( getAncestors( b ) ).to.deep.equal( [ div, p1, span, b ] );
+	} );
+
+	it( 'should not return document object', () => {
+		const span = createElement( document, 'span' );
+		document.documentElement.appendChild( span );
+
+		const ancestors = getAncestors( span );
+
+		expect( ancestors.includes( document ) ).to.be.false;
+	} );
+
+	it( 'should not return any non-Node, non-DocumentFragment object if given node is in iframe', () => {
+		const iframe = document.createElement( 'iframe' );
+		document.body.appendChild( iframe );
+
+		const iframeDoc = iframe.contentWindow.document;
+
+		const span = createElement( iframeDoc, 'span' );
+		iframeDoc.documentElement.appendChild( span );
+
+		const ancestors = getAncestors( span );
+
+		expect( ancestors.includes( iframeDoc ) ).to.be.false;
+
+		document.body.removeChild( iframe );
+	} );
+} );

+ 63 - 0
packages/ckeditor5-utils/tests/dom/getcommonancestor.js

@@ -0,0 +1,63 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+/* bender-tags: dom, browser-only */
+
+import getCommonAncestor from '/ckeditor5/utils/dom/getcommonancestor.js';
+import createElement from '/ckeditor5/utils/dom/createelement.js';
+
+describe( 'getParents', () => {
+	let b, span1, span2, p1, p2, i, div;
+
+	beforeEach( () => {
+		// DIV
+		//  |- P (1)
+		//  |  |- SPAN (1)
+		//  |  |  |- B
+		//  |  |
+		//  |  |- SPAN (2)
+		//  |
+		//  |- P (2)
+		//     |- I
+		b = createElement( document, 'b' );
+		span1 = createElement( document, 'span', {}, [ b ] );
+		span2 = createElement( document, 'span' );
+		p1 = createElement( document, 'p', {}, [ span1, span2 ] );
+		i = createElement( document, 'i' );
+		p2 = createElement( document, 'p', {}, [ i ] );
+		div = createElement( document, 'div', {}, [ p1, p2 ] );
+	} );
+
+	function test( a, b, lca ) {
+		expect( getCommonAncestor( a, b ) ).to.equal( lca );
+		expect( getCommonAncestor( b, a ) ).to.equal( lca );
+	}
+
+	it( 'should return lowest common ancestor of nodes in different tree branches', () => {
+		test( p1, p2, div );
+		test( span1, span2, p1 );
+		test( b, span2, p1 );
+		test( i, b, div );
+	} );
+
+	it( 'should return one of nodes if it is a parent of another node', () => {
+		test( div, p1, div );
+		test( p1, b, p1 );
+	} );
+
+	it( 'should return the node if both parameters are same', () => {
+		test( div, div, div );
+		test( b, b, b );
+	} );
+
+	it( 'should return null for nodes that do not have common ancestor (different trees)', () => {
+		const diffB = createElement( document, 'b' );
+		const diffDiv = createElement( document, 'div', {}, diffB );
+
+		test( diffB, span1, null );
+		test( diffDiv, p1, null );
+	} );
+} );