123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- /**
- * Analyzes DOM queries done via native DOM methods
- */
- /* global Element: true, Document: true, Node: true, window: true */
- exports.version = '0.10.a';
- exports.module = function(phantomas) {
- 'use strict';
- phantomas.setMetric('DOMqueries'); // @desc number of all DOM queries @offenders
- phantomas.setMetric('DOMqueriesWithoutResults'); // @desc number of DOM queries that retutned nothing @offenders
- phantomas.setMetric('DOMqueriesById'); // @desc number of document.getElementById calls
- phantomas.setMetric('DOMqueriesByClassName'); // @desc number of document.getElementsByClassName calls
- phantomas.setMetric('DOMqueriesByTagName'); // @desc number of document.getElementsByTagName calls
- phantomas.setMetric('DOMqueriesByQuerySelectorAll'); // @desc number of document.querySelector(All) calls
- phantomas.setMetric('DOMinserts'); // @desc number of DOM nodes inserts
- phantomas.setMetric('DOMqueriesDuplicated'); // @desc number of DOM queries called more than once
- phantomas.setMetric('DOMqueriesAvoidable'); // @desc number of repeated uses of a duplicated query
- // fake native DOM functions
- phantomas.once('init', function() {
- phantomas.evaluate(function() {
- (function(phantomas) {
- function querySpy(type, query, fnName, context, hasNoResults) {
- phantomas.emit('domQuery', type, query, fnName, context, hasNoResults); // @desc DOM query has been made
- }
- phantomas.spy(Document.prototype, 'getElementById', function(id) {
- phantomas.incrMetric('DOMqueriesById');
- phantomas.addOffender('DOMqueriesById', '#%s (in %s)', id, '#document');
- phantomas.enterContext({
- type: 'getElementById',
- callDetails: {
- arguments: ['#' + id]
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result, args) {
- var id = args[0];
- querySpy('id', '#' + id, 'getElementById', '#document', (result === null));
- var moreData = {
- resultsNumber : (result === null) ? 0 : 1
- };
- phantomas.leaveContext(moreData);
- });
- // selectors by class name
- function selectorClassNameSpyBefore(className) {
- /*jshint validthis: true */
- var context = phantomas.getDOMPath(this);
- phantomas.incrMetric('DOMqueriesByClassName');
- phantomas.addOffender('DOMqueriesByClassName', '.%s (in %s)', className, context);
- phantomas.enterContext({
- type: 'getElementsByClassName',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: ['.' + className]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- function selectorClassNameAfter(result, args) {
- /*jshint validthis: true */
- var className = args[0];
- var context = phantomas.getDOMPath(this);
- querySpy('class', '.' + className, 'getElementsByClassName', context, (result.length === 0));
-
- var moreData = {
- resultsNumber : (result && result.length > 0) ? result.length : 0
- };
- phantomas.leaveContext(moreData);
- }
- phantomas.spy(Document.prototype, 'getElementsByClassName', selectorClassNameSpyBefore, selectorClassNameAfter);
- phantomas.spy(Element.prototype, 'getElementsByClassName', selectorClassNameSpyBefore, selectorClassNameAfter);
- // selectors by tag name
- function selectorTagNameSpyBefore(tagName) {
- /*jshint validthis: true */
- var context = phantomas.getDOMPath(this);
- phantomas.incrMetric('DOMqueriesByTagName');
- phantomas.addOffender('DOMqueriesByTagName', '%s (in %s)', tagName, context);
- phantomas.enterContext({
- type: 'getElementsByTagName',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: [tagName]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- function selectorTagNameSpyAfter(result, args) {
- /*jshint validthis: true */
-
- var tagName = args[0];
- var context = phantomas.getDOMPath(this);
- querySpy('tag name', tagName.toLowerCase(), 'getElementsByTagName', context, (result.length === 0));
-
- var moreData = {
- resultsNumber : (result && result.length > 0) ? result.length : 0
- };
- phantomas.leaveContext(moreData);
- }
- phantomas.spy(Document.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter);
- phantomas.spy(Element.prototype, 'getElementsByTagName', selectorTagNameSpyBefore, selectorTagNameSpyAfter);
- // selector queries
- function selectorQuerySpy(selector, context) {
- phantomas.incrMetric('DOMqueriesByQuerySelectorAll');
- phantomas.addOffender('DOMqueriesByQuerySelectorAll', '%s (in %s)', selector, context);
- }
- function selectorQuerySpyBefore(selector) {
- /*jshint validthis: true */
- var context = phantomas.getDOMPath(this);
- selectorQuerySpy(selector, context);
- phantomas.enterContext({
- type: 'querySelector',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: [selector]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- function selectorQuerySpyAfter(result, args) {
- /*jshint validthis: true */
- var selector = args[0];
- var context = phantomas.getDOMPath(this);
- querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0));
-
- var moreData = {
- resultsNumber : result ? 1 : 0
- };
- phantomas.leaveContext(moreData);
- }
- function selectorAllQuerySpyBefore(selector) {
- /*jshint validthis: true */
- var context = phantomas.getDOMPath(this);
- selectorQuerySpy(selector, context);
- phantomas.enterContext({
- type: 'querySelectorAll',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: [selector]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- function selectorAllQuerySpryAfter(result, args) {
- /*jshint validthis: true */
- var selector = args[0];
- var context = phantomas.getDOMPath(this);
- querySpy('selector', selector, 'querySelectorAll', context, (!result || result.length === 0));
- var moreData = {
- resultsNumber : (result && result.length > 0) ? result.length : 0
- };
- phantomas.leaveContext(moreData);
- }
- phantomas.spy(Document.prototype, 'querySelector', selectorQuerySpyBefore, selectorQuerySpyAfter);
- phantomas.spy(Document.prototype, 'querySelectorAll', selectorAllQuerySpyBefore, selectorAllQuerySpryAfter);
- phantomas.spy(Element.prototype, 'querySelector', selectorQuerySpyBefore, selectorQuerySpyAfter);
- phantomas.spy(Element.prototype, 'querySelectorAll', selectorAllQuerySpyBefore, selectorAllQuerySpryAfter);
- // count DOM inserts
- function appendChild(child, element, context, appended) {
- /*jshint validthis: true */
- // ignore appending to the node that's not yet added to DOM tree
- if (!element.parentNode) {
- return;
- }
- // don't count elements added to fragments as a DOM inserts (issue #350)
- // DocumentFragment > div[0]
- if (context.indexOf('DocumentFragment') === 0) {
- return;
- }
- phantomas.incrMetric('DOMinserts');
- phantomas.addOffender('DOMinserts', '"%s" appended to "%s"', appended, context);
- //phantomas.log('DOM insert: node "%s" appended to "%s"', appended, context);
- }
- function appendChildSpyBefore(child) {
- /*jshint validthis: true */
- var context = phantomas.getDOMPath(this);
- var appended = phantomas.getDOMPath(child);
- appendChild(child, this, context, appended);
- phantomas.enterContext({
- type: 'appendChild',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: [appended]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- function insertBeforeSpyBefore(child, refElement) {
- /*jshint validthis: true */
-
- var context = phantomas.getDOMPath(this);
- var appended = phantomas.getDOMPath(child);
- var referent = phantomas.getDOMPath(refElement);
- appendChild(child, this, context, appended);
- phantomas.enterContext({
- type: 'insertBefore',
- callDetails: {
- context: {
- length: 1,
- elements: [context]
- },
- arguments: [
- appended,
- referent
- ]
- },
- backtrace: phantomas.getBacktrace()
- });
- }
- phantomas.spy(Node.prototype, 'appendChild', appendChildSpyBefore, function(result) {
- phantomas.leaveContext();
- });
- phantomas.spy(Node.prototype, 'insertBefore', insertBeforeSpyBefore, function(result) {
- phantomas.leaveContext();
- });
- phantomas.spy(Document.prototype, 'createElement', function(tagName) {
-
- phantomas.enterContext({
- type: 'createElement',
- callDetails: {
- arguments: [tagName]
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result, args) {
- phantomas.leaveContext();
- });
- phantomas.spy(Document.prototype, 'createTextNode', function(text) {
-
- phantomas.enterContext({
- type: 'createTextNode',
- callDetails: {
- arguments: [text]
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result, args) {
- phantomas.leaveContext();
- });
- phantomas.spy(Document.prototype, 'createDocumentFragment', function() {
-
- phantomas.enterContext({
- type: 'createDocumentFragment',
- callDetails: {
- arguments: []
- },
- backtrace: phantomas.getBacktrace()
- });
- }, function(result, args) {
- phantomas.leaveContext();
- });
- })(window.__phantomas);
- });
- });
- // report DOM queries that return no results (issue #420)
- phantomas.on('domQuery', function(type, query, fnName, context, hasNoResults) {
- // ignore DOM queries within DOM fragments (used internally by jQuery)
- if (context.indexOf('body') !== 0 && context.indexOf('#document') !== 0) {
- return;
- }
- if (hasNoResults === true) {
- phantomas.incrMetric('DOMqueriesWithoutResults');
- phantomas.addOffender('DOMqueriesWithoutResults', '%s (in %s) using %s', query, context, fnName);
- }
- });
- // count DOM queries by either ID, tag name, class name and selector query
- // @see https://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-doctype
- var Collection = require('../../util/collection'),
- DOMqueries = new Collection();
- phantomas.on('domQuery', function(type, query, fnName, context) {
- phantomas.log('DOM query: by %s - "%s" (using %s) in %s', type, query, fnName, context);
- phantomas.incrMetric('DOMqueries');
- if (context && context.indexOf('DocumentFragment') === -1) {
- DOMqueries.push(type + ' "' + query + '" with ' + fnName + ' (in context ' + context + ')');
- }
- });
- phantomas.on('report', function() {
- DOMqueries.sort().forEach(function(query, cnt) {
- if (cnt > 1) {
- phantomas.incrMetric('DOMqueriesDuplicated');
- phantomas.incrMetric('DOMqueriesAvoidable', cnt - 1);
- phantomas.addOffender('DOMqueriesDuplicated', '%s: %d queries', query, cnt);
- }
- });
- });
- };
|