Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fixes ticket issue mootools#13, feature detections are now cached. Ch…
…anged a little the speedtest, it shows impressive results.
  • Loading branch information
fabiomcosta committed Feb 20, 2011
1 parent 453edbb commit 77e3967
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 35 deletions.
89 changes: 55 additions & 34 deletions Source/Slick.Finder.js
Expand Up @@ -9,7 +9,9 @@ requires: Slick.Parser


;(function(){ ;(function(){


var local = {}; var local = {},
featuresCache = {},
toString = Object.prototype.toString;


// Feature / Bug detection // Feature / Bug detection


Expand All @@ -18,7 +20,7 @@ local.isNativeCode = function(fn){
}; };


local.isXML = function(document){ local.isXML = function(document){
return (!!document.xmlVersion) || (!!document.xml) || (Object.prototype.toString.call(document) == '[object XMLDocument]') || return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
(document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
}; };


Expand All @@ -35,19 +37,34 @@ local.setDocument = function(document){


if (this.document === document) return; if (this.document === document) return;
this.document = document; this.document = document;
var root = this.root = document.documentElement;

// check if we have done feature detection on this document before
this.isXMLDocument = this.isXML(document);

var root = document.documentElement,
this.brokenStarGEBTN rootUid = this.getUIDXML(root),
= this.starSelectsClosedQSA features = featuresCache[rootUid];
= this.idGetsName
= this.brokenMixedCaseQSA if (features){
= this.brokenGEBCN for (feature in features){
= this.brokenCheckedQSA this[feature] = features[feature];
= this.brokenEmptyAttributeQSA }
= this.isHTMLDocument return;
= this.nativeMatchesSelector }

features = featuresCache[rootUid] = {};

features.root = root;
features.isXMLDocument = this.isXML(document);

features.brokenStarGEBTN
= features.starSelectsClosedQSA
= features.idGetsName
= features.brokenMixedCaseQSA
= features.brokenGEBCN
= features.brokenCheckedQSA
= features.brokenEmptyAttributeQSA
= features.isHTMLDocument
= features.nativeMatchesSelector
= false; = false;


var starSelectsClosed, starSelectsComments, var starSelectsClosed, starSelectsComments,
Expand All @@ -63,10 +80,10 @@ local.setDocument = function(document){
// on non-HTML documents innerHTML and getElementsById doesnt work properly // on non-HTML documents innerHTML and getElementsById doesnt work properly
try { try {
testNode.innerHTML = '<a id="'+id+'"></a>'; testNode.innerHTML = '<a id="'+id+'"></a>';
this.isHTMLDocument = !!document.getElementById(id); features.isHTMLDocument = !!document.getElementById(id);
} catch(e){}; } catch(e){};


if (this.isHTMLDocument){ if (features.isHTMLDocument){


testNode.style.display = 'none'; testNode.style.display = 'none';


Expand All @@ -81,12 +98,12 @@ local.setDocument = function(document){
starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
} catch(e){}; } catch(e){};


this.brokenStarGEBTN = starSelectsComments || starSelectsClosed; features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;


// IE returns elements with the name instead of just id for getElementsById for some documents // IE returns elements with the name instead of just id for getElementsById for some documents
try { try {
testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'; testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
this.idGetsName = document.getElementById(id) === testNode.firstChild; features.idGetsName = document.getElementById(id) === testNode.firstChild;
} catch(e){}; } catch(e){};


if (testNode.getElementsByClassName){ if (testNode.getElementsByClassName){
Expand All @@ -100,38 +117,38 @@ local.setDocument = function(document){
} catch(e){}; } catch(e){};


// Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
if (testNode.getElementsByClassName) try { try {
testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>'; testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
} catch(e){}; } catch(e){};


this.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
} }


if (testNode.querySelectorAll){ if (testNode.querySelectorAll){
// IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
try { try {
testNode.innerHTML = 'foo</foo>'; testNode.innerHTML = 'foo</foo>';
selected = testNode.querySelectorAll('*'); selected = testNode.querySelectorAll('*');
this.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
} catch(e){}; } catch(e){};


// Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
try { try {
testNode.innerHTML = '<a class="MiX"></a>'; testNode.innerHTML = '<a class="MiX"></a>';
this.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length; features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
} catch(e){}; } catch(e){};


// Webkit and Opera dont return selected options on querySelectorAll // Webkit and Opera dont return selected options on querySelectorAll
try { try {
testNode.innerHTML = '<select><option selected="selected">a</option></select>'; testNode.innerHTML = '<select><option selected="selected">a</option></select>';
this.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
} catch(e){}; } catch(e){};


// IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
try { try {
testNode.innerHTML = '<a class=""></a>'; testNode.innerHTML = '<a class=""></a>';
this.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
} catch(e){}; } catch(e){};


} }
Expand All @@ -144,11 +161,11 @@ local.setDocument = function(document){


// native matchesSelector function // native matchesSelector function


this.nativeMatchesSelector = root.matchesSelector || root.msMatchesSelector || root.mozMatchesSelector || root.webkitMatchesSelector; features.nativeMatchesSelector = root.matchesSelector || root.msMatchesSelector || root.mozMatchesSelector || root.webkitMatchesSelector;
if (this.nativeMatchesSelector) try { if (features.nativeMatchesSelector) try {
// if matchesSelector trows errors on incorrect sintaxes we can use it // if matchesSelector trows errors on incorrect sintaxes we can use it
this.nativeMatchesSelector.call(root, ':slick'); features.nativeMatchesSelector.call(root, ':slick');
this.nativeMatchesSelector = null; features.nativeMatchesSelector = null;
} catch(e){}; } catch(e){};


} }
Expand All @@ -158,7 +175,7 @@ local.setDocument = function(document){


// getAttribute // getAttribute


this.getAttribute = (this.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
var method = this.attributeGetters[name]; var method = this.attributeGetters[name];
if (method) return method.call(node); if (method) return method.call(node);
var attributeNode = node.getAttributeNode(name); var attributeNode = node.getAttributeNode(name);
Expand All @@ -170,7 +187,7 @@ local.setDocument = function(document){


// hasAttribute // hasAttribute


this.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) { features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
return node.hasAttribute(attribute); return node.hasAttribute(attribute);
} : function(node, attribute) { } : function(node, attribute) {
node = node.getAttributeNode(attribute); node = node.getAttributeNode(attribute);
Expand All @@ -179,7 +196,7 @@ local.setDocument = function(document){


// contains // contains
// FIXME: Add specs: local.contains should be different for xml and html documents? // FIXME: Add specs: local.contains should be different for xml and html documents?
this.contains = (root && this.isNativeCode(root.contains)) ? function(context, node){ features.contains = (root && this.isNativeCode(root.contains)) ? function(context, node){
return context.contains(node); return context.contains(node);
} : (root && root.compareDocumentPosition) ? function(context, node){ } : (root && root.compareDocumentPosition) ? function(context, node){
return context === node || !!(context.compareDocumentPosition(node) & 16); return context === node || !!(context.compareDocumentPosition(node) & 16);
Expand All @@ -193,7 +210,7 @@ local.setDocument = function(document){
// document order sorting // document order sorting
// credits to Sizzle (http://sizzlejs.com/) // credits to Sizzle (http://sizzlejs.com/)


this.documentSorter = (root.compareDocumentPosition) ? function(a, b){ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
} : ('sourceIndex' in root) ? function(a, b){ } : ('sourceIndex' in root) ? function(a, b){
Expand All @@ -209,9 +226,13 @@ local.setDocument = function(document){
return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
} : null ; } : null ;


this.getUID = (this.isHTMLDocument) ? this.getUIDHTML : this.getUIDXML; features.getUID = (features.isHTMLDocument) ? this.getUIDHTML : this.getUIDXML;


root = null; root = null;

for (feature in features){
this[feature] = features[feature];
}
}; };


// Main Method // Main Method
Expand Down
5 changes: 4 additions & 1 deletion speed/speedtests/setdocument/index.html
Expand Up @@ -5,10 +5,11 @@
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>setdocument</title> <title>setdocument</title>
<script type="text/javascript" src="../../benchmarkjs/benchmark.js"></script> <script type="text/javascript" src="../../benchmarkjs/benchmark.js"></script>
<script src="https://github.com/mootools/slick/raw/d3aef7cb747c127f2dd33a608769678ed4e7f9ef/Source/Slick.Parser.js" type="text/javascript" charset="utf-8"></script>
<script src="https://github.com/mootools/slick/raw/d3aef7cb747c127f2dd33a608769678ed4e7f9ef/Source/Slick.Finder.js" type="text/javascript" charset="utf-8"></script> <script src="https://github.com/mootools/slick/raw/d3aef7cb747c127f2dd33a608769678ed4e7f9ef/Source/Slick.Finder.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript"> <script type="text/javascript">
oldSlick = window.Slick; oldSlick = window.Slick;
delete window.Slick; Slick = null;
</script> </script>
<script src="../../../Source/Slick.Parser.js" type="text/javascript"></script> <script src="../../../Source/Slick.Parser.js" type="text/javascript"></script>
<script src="../../../Source/Slick.Finder.js" type="text/javascript"></script> <script src="../../../Source/Slick.Finder.js" type="text/javascript"></script>
Expand All @@ -17,6 +18,7 @@
<body> <body>
<applet code="nano" archive="../../benchmarkjs/nano.jar"></applet> <applet code="nano" archive="../../benchmarkjs/nano.jar"></applet>
<script type="text/javascript"> <script type="text/javascript">

var suite = new Benchmark.Suite; var suite = new Benchmark.Suite;
// add tests // add tests
suite suite
Expand All @@ -36,6 +38,7 @@
console.log('Fastest is ' + this.filter('fastest').pluck('name')); console.log('Fastest is ' + this.filter('fastest').pluck('name'));
}) })
.run(true); .run(true);

</script> </script>
</body> </body>
</html> </html>

0 comments on commit 77e3967

Please sign in to comment.