blob: 401e04409d94d10a45db0bf00c5e7932b53b132f [file] [log] [blame]
"use strict";
var defineGetter = require("../utils").defineGetter;
module.exports = function (core) {
var DOCUMENT_POSITION_DISCONNECTED = core.Node.DOCUMENT_POSITION_DISCONNECTED;
var DOCUMENT_POSITION_PRECEDING = core.Node.DOCUMENT_POSITION_PRECEDING;
var DOCUMENT_POSITION_FOLLOWING = core.Node.DOCUMENT_POSITION_FOLLOWING;
var DOCUMENT_POSITION_CONTAINS = core.Node.DOCUMENT_POSITION_CONTAINS;
var DOCUMENT_POSITION_CONTAINED_BY = core.Node.DOCUMENT_POSITION_CONTAINED_BY;
var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = core.Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
/**
* Return true if node is of a type obsoleted by the WHATWG living standard
* @param {Node} node
* @return {Boolean}
*/
function isObsoleteNodeType(node) {
return node.nodeType === core.Node.ENTITY_NODE ||
node.nodeType === core.Node.ENTITY_REFERENCE_NODE ||
node.nodeType === core.Node.NOTATION_NODE ||
node.nodeType === core.Node.CDATA_SECTION_NODE;
}
/**
* Return the parent node of node, whatever its nodeType is
* @param {Node} node
* @return {Node or null}
*/
function getNodeParent(node) {
if(!node) {
return node;
}
switch (node.nodeType) {
case core.Node.DOCUMENT_NODE:
case core.Node.DOCUMENT_FRAGMENT_NODE:
return null;
case core.Node.COMMENT_NODE:
case core.Node.DOCUMENT_TYPE_NODE:
case core.Node.ELEMENT_NODE:
case core.Node.PROCESSING_INSTRUCTION_NODE:
case core.Node.TEXT_NODE:
return node.parentNode;
case core.Node.ATTRIBUTE_NODE:
return node._parentNode;
default:
throw new Error("Unknown node type:" + node.nodeType);
}
}
/**
* Walk up the node tree and return the nodes root node
* @param {Node} node
* @return {Node}
*/
function findNodeRoot(node) {
if (!getNodeParent(node)) {
return node;
}
return findNodeRoot(getNodeParent(node));
}
/**
* Walk up the node tree returning true if otherNode is an ancestor of node
* @param {Node} node
* @param {Node} otherNode
* @return {Boolean}
*/
function isAncestor(node, otherNode) {
var parentNode = node.nodeType === node.ATTRIBUTE_NODE ? node._parentNode : node.parentNode;
if (!parentNode) {
return false;
}
if(parentNode === otherNode) {
return true;
}
return isAncestor(parentNode, otherNode);
}
/**
* Traverse the node tree starting at current. Return DOCUMENT_POSITION_FOLLOWING if otherNode follows node. Return
* DOCUMENT_POSITION_PRECEDING if otherNode precedes node
* @param {Node} current
* @param {Node} node
* @param {Node} otherNode
* @return {Number}
*/
function followingOrPreceding(current, node, otherNode) {
if (current === node) {
return core.Node.DOCUMENT_POSITION_FOLLOWING;
}
if (current === otherNode) {
return core.Node.DOCUMENT_POSITION_PRECEDING;
}
var i = 0, len = current._childNodes.length, child, result;
for(; i < len; i += 1) {
child = current._childNodes[i];
if((result = followingOrPreceding(child, node, otherNode)) !== 0) {
return result;
}
}
return 0;
}
/**
* Returns a bitmask Number composed of DOCUMENT_POSITION constants based upon the rules defined in
* http://dom.spec.whatwg.org/#dom-node-comparedocumentposition
* @param {Node} other
* @return {Number}
*/
core.Node.prototype.compareDocumentPosition = function compareDocumentPosition (other) {
// Let reference be the context object.
var reference = this;
if(!(other instanceof core.Node)) {
throw Error("Comparing position against non-Node values is not allowed");
}
if (isObsoleteNodeType(reference) || isObsoleteNodeType(other)) {
throw new Error("Obsolete node type");
}
// If other and reference are the same object, return zero.
if(reference.isSameNode(other)) {
return 0;
}
// If other and reference are not in the same tree, return the result of adding DOCUMENT_POSITION_DISCONNECTED,
// DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING,
// with the constraint that this is to be consistent, together.
if(findNodeRoot(reference) !== findNodeRoot(other)) {
return DOCUMENT_POSITION_DISCONNECTED + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING;
}
// If other is an ancestor of reference, return the result of adding DOCUMENT_POSITION_CONTAINS to
// DOCUMENT_POSITION_PRECEDING.
if(isAncestor(reference, other)) {
return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING;
}
// If other is a descendant of reference, return the result of adding DOCUMENT_POSITION_CONTAINED_BY to
// DOCUMENT_POSITION_FOLLOWING.
if(isAncestor(other, reference)) {
return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING;
}
// If other is preceding reference return DOCUMENT_POSITION_PRECEDING, otherwise return DOCUMENT_POSITION_FOLLOWING
return followingOrPreceding(findNodeRoot(reference), reference, other);
};
/**
* The contains(other) method returns true if other is an inclusive descendant of the context object,
* and false otherwise (including when other is null).
* @param {[Node]} other [the node to test]
* @return {[boolean]} [whether other is an inclusive descendant of this]
*/
core.Node.prototype.contains = function (other) {
return other instanceof core.Node &&
(this.isSameNode(other) || !!(this.compareDocumentPosition(other) & DOCUMENT_POSITION_CONTAINED_BY));
};
// http://dom.spec.whatwg.org/#dom-node-parentelement
defineGetter(core.Node.prototype, "parentElement", function () {
return this._parentNode !== null && this._parentNode.nodeType === core.Node.ELEMENT_NODE ? this._parentNode : null;
});
};