blob: a84c286f4680641e0cfcfce148fecd9544e776f4 [file] [log] [blame]
/*
* Copyright (C) 2007-2018 Diego Perini
* All rights reserved.
*
* CSS3 pseudo-classes extension for NWMatcher
*
* Added capabilities:
*
* - structural pseudo-classes
*
* :root, :empty,
* :nth-child(), nth-of-type(),
* :nth-last-child(), nth-last-of-type(),
* :first-child, :last-child, :only-child
* :first-of-type, :last-of-type, :only-of-type
*
* - negation, language, target and UI element pseudo-classes
*
* :not(), :target, :lang(), :target
* :link, :visited, :active, :focus, :hover,
* :checked, :disabled, :enabled, :selected
*/
(function(global) {
var LINK_NODES = {
'a': 1, 'A': 1,
'area': 1, 'AREA': 1,
'link': 1, 'LINK': 1
},
root = document.documentElement,
contains = 'compareDocumentPosition' in root ?
function(container, element) {
return (container.compareDocumentPosition(element) & 16) == 16;
} : 'contains' in root ?
function(container, element) {
return element.nodeType == 1 && container.contains(element);
} :
function(container, element) {
while ((element = element.parentNode) && element.nodeType == 1) {
if (element === container) return true;
}
return false;
},
isLink =
function(element) {
return element.getAttribute('href') && LINK_NODES[element.nodeName];
},
isEmpty =
function(node) {
node = node.firstChild;
while (node) {
if (node.nodeType == 3 || node.nodeName > '@') return false;
node = node.nextSibling;
}
return true;
},
nthElement =
function(element, last) {
var count = 1, succ = last ? 'nextSibling' : 'previousSibling';
while ((element = element[succ])) {
if (element.nodeName > '@') ++count;
}
return count;
},
nthOfType =
function(element, last) {
var count = 1, succ = last ? 'nextSibling' : 'previousSibling', type = element.nodeName;
while ((element = element[succ])) {
if (element.nodeName == type) ++count;
}
return count;
};
NW.Dom.Snapshot['contains'] = contains;
NW.Dom.Snapshot['isLink'] = isLink;
NW.Dom.Snapshot['isEmpty'] = isEmpty;
NW.Dom.Snapshot['nthOfType'] = nthOfType;
NW.Dom.Snapshot['nthElement'] = nthElement;
})(this);
NW.Dom.registerSelector(
'nwmatcher:spseudos',
/^\:(root|empty|(?:first|last|only)(?:-child|-of-type)|nth(?:-last)?(?:-child|-of-type)\(\s*(even|odd|(?:[-+]{0,1}\d*n\s*)?[-+]{0,1}\s*\d*)\s*\))?(.*)/i,
(function(global) {
return function(match, source) {
var a, n, b, status = true, test, type;
switch (match[1]) {
case 'root':
if (match[3])
source = 'if(e===h||s.contains(h,e)){' + source + '}';
else
source = 'if(e===h){' + source + '}';
break;
case 'empty':
source = 'if(s.isEmpty(e)){' + source + '}';
break;
default:
if (match[1] && match[2]) {
if (match[2] == 'n') {
source = 'if(e!==h){' + source + '}';
break;
} else if (match[2] == 'even') {
a = 2;
b = 0;
} else if (match[2] == 'odd') {
a = 2;
b = 1;
} else {
b = ((n = match[2].match(/(-?\d+)$/)) ? parseInt(n[1], 10) : 0);
a = ((n = match[2].match(/(-?\d*)n/i)) ? parseInt(n[1], 10) : 0);
if (n && n[1] == '-') a = -1;
}
test = a > 1 ?
(/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' :
'n>=' + b + '&&(n-(' + b + '))%' + a + '==0' : a < -1 ?
(/last/i.test(match[1])) ? '(n-(' + b + '))%' + a + '==0' :
'n<=' + b + '&&(n-(' + b + '))%' + a + '==0' : a === 0 ?
'n==' + b : a == -1 ? 'n<=' + b : 'n>=' + b;
source =
'if(e!==h){' +
'n=s[' + (/-of-type/i.test(match[1]) ? '"nthOfType"' : '"nthElement"') + ']' +
'(e,' + (/last/i.test(match[1]) ? 'true' : 'false') + ');' +
'if(' + test + '){' + source + '}' +
'}';
} else if (match[1]) {
a = /first/i.test(match[1]) ? 'previous' : 'next';
n = /only/i.test(match[1]) ? 'previous' : 'next';
b = /first|last/i.test(match[1]);
type = /-of-type/i.test(match[1]) ? '&&n.nodeName!==e.nodeName' : '&&n.nodeName<"@"';
source = 'if(e!==h){' +
( 'n=e;while((n=n.' + a + 'Sibling)' + type + ');if(!n){' + (b ? source :
'n=e;while((n=n.' + n + 'Sibling)' + type + ');if(!n){' + source + '}') + '}' ) + '}';
} else {
status = false;
}
break;
}
return {
'source': source,
'status': status
};
};
})(this));
NW.Dom.registerSelector(
'nwmatcher:dpseudos',
/^\:(link|visited|target|active|focus|hover|checked|disabled|enabled|selected|lang\(([-\w]{2,})\)|not\(\s*(:nth(?:-last)?(?:-child|-of-type)\(\s*(?:even|odd|(?:[-+]{0,1}\d*n\s*)?[-+]{0,1}\s*\d*)\s*\)|[^()]*)\s*\))?(.*)/i,
(function(global) {
var doc = global.document,
Config = NW.Dom.Config,
Tokens = NW.Dom.Tokens,
reTrimSpace = RegExp('^\\s+|\\s+$', 'g'),
reSimpleNot = RegExp('^((?!:not)' +
'(' + Tokens.prefixes + '|' + Tokens.identifier +
'|\\([^()]*\\))+|\\[' + Tokens.attributes + '\\])$');
return function(match, source) {
var expr, status = true, test;
switch (match[1].match(/^\w+/)[0]) {
case 'not':
expr = match[3].replace(reTrimSpace, '');
if (Config.SIMPLENOT && !reSimpleNot.test(expr)) {
NW.Dom.emit('Negation pseudo-class only accepts simple selectors "' + selector + '"');
} else {
if ('compatMode' in doc) {
source = 'if(!' + NW.Dom.compile(expr, '', false) + '(e,s,d,h,g)){' + source + '}';
} else {
source = 'if(!s.match(e, "' + expr.replace(/\x22/g, '\\"') + '",g)){' + source +'}';
}
}
break;
case 'checked':
source = 'if((typeof e.form!=="undefined"&&(/^(?:radio|checkbox)$/i).test(e.type)&&e.checked)' +
(Config.USE_HTML5 ? '||(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' : '') +
'){' + source + '}';
break;
case 'disabled':
source = 'if(((typeof e.form!=="undefined"' +
(Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') +
')||s.isLink(e))&&e.disabled===true){' + source + '}';
break;
case 'enabled':
source = 'if(((typeof e.form!=="undefined"' +
(Config.USE_HTML5 ? '' : '&&!(/^hidden$/i).test(e.type)') +
')||s.isLink(e))&&e.disabled===false){' + source + '}';
break;
case 'lang':
test = '';
if (match[2]) test = match[2].substr(0, 2) + '-';
source = 'do{(n=e.lang||"").toLowerCase();' +
'if((n==""&&h.lang=="' + match[2].toLowerCase() + '")||' +
'(n&&(n=="' + match[2].toLowerCase() +
'"||n.substr(0,3)=="' + test.toLowerCase() + '")))' +
'{' + source + 'break;}}while((e=e.parentNode)&&e!==g);';
break;
case 'target':
source = 'if(e.id==d.location.hash.slice(1)){' + source + '}';
break;
case 'link':
source = 'if(s.isLink(e)&&!e.visited){' + source + '}';
break;
case 'visited':
source = 'if(s.isLink(e)&&e.visited){' + source + '}';
break;
case 'active':
source = 'if(e===d.activeElement){' + source + '}';
break;
case 'hover':
source = 'if(e===d.hoverElement){' + source + '}';
break;
case 'focus':
source = 'hasFocus' in doc ?
'if(e===d.activeElement&&d.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number")){' + source + '}' :
'if(e===d.activeElement&&(e.type||e.href)){' + source + '}';
break;
case 'selected':
source = 'if(/^option$/i.test(e.nodeName)&&(e.selected||e.checked)){' + source + '}';
break;
default:
status = false;
break;
}
return {
'source': source,
'status': status
};
};
})(this));
NW.Dom.registerSelector(
'nwmatcher:epseudos',
/^((?:[:]{1,2}(?:after|before|first-letter|first-line))|(?:[:]{2,2}(?:selection|backdrop|placeholder)))?(.*)/i,
(function(global) {
return function(match, source) {
var status = true;
switch (match[1].match(/(\w+)$/)[0]) {
case 'after':
case 'before':
case 'first-letter':
case 'first-line':
case 'selection':
case 'backdrop':
case 'placeholder':
source = 'if(!(/1|11/).test(e.nodeType)){' + source + '}';
break;
default:
status = false;
break;
}
return {
'source': source,
'status': status
};
};
})(this));