diff --git a/front/src/views/rule.html b/front/src/views/rule.html index c62e14f..fcc6ae0 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -62,6 +62,19 @@ +
+ {{offender.writeFn}} + + called from + {{offender.from.functionName}}() + + line {{offender.from.line}} + + + called from (no backtrace available) + +
+
{{offender.error}} diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 2406e45..212b020 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -242,7 +242,49 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 10, "isAbnormalThreshold": 20, - "hasOffenders": false + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^document.write(ln)?\(\) used from (.*)$/.exec(offender); + + if (parts) { + + var writeFn = 'document.write' + (parts[1] || ''); + + var methodParts = /^([^\s]+) \((.+):(\d+)\)$/.exec(parts[2]); + if (methodParts) { + return { + writeFn: writeFn, + from: { + functionName: methodParts[1], + file: methodParts[2], + line: methodParts[3] + } + }; + } else { + var noMethodParts = /^(.+):(\d+)$/.exec(parts[2]); + + if (noMethodParts) { + return { + writeFn: writeFn, + from: { + file: noMethodParts[1], + line: noMethodParts[2] + } + }; + } + } + } + + debug('documentWriteCalls offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + }) + }; + } }, "consoleMessages": { "tool": "phantomas", diff --git a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js index b4a97e9..4ec31ef 100644 --- a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js +++ b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js @@ -36,7 +36,7 @@ exports.module = function(phantomas) { phantomas.log('Overwritting phantomas spy function'); function spy(obj, fn, callbackBefore, callbackAfter) { - var origFn = obj[fn]; + var origFn = obj && obj[fn]; if (typeof origFn !== 'function') { return false; diff --git a/lib/tools/phantomas/custom_modules/modules/cachYLT/cachYLT.js b/lib/tools/phantomas/custom_modules/modules/cachYLT/cachYLT.js deleted file mode 100644 index 213edfe..0000000 --- a/lib/tools/phantomas/custom_modules/modules/cachYLT/cachYLT.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Analyzes HTTP caching headers - * - * @see https://developers.google.com/speed/docs/best-practices/caching - */ - -exports.version = '0.2.a'; - -exports.module = function(phantomas) { - 'use strict'; - - var cacheControlRegExp = /max-age=(\d+)/; - - function getCachingTime(url, headers) { - // false means "no caching" - var ttl = false, - headerName, - now = new Date(), - headerDate; - - for (headerName in headers) { - var value = headers[headerName]; - - switch (headerName.toLowerCase()) { - // parse max-age=... - // - // max-age=2592000 - // public, max-age=300, must-revalidate - case 'cache-control': - var matches = value.match(cacheControlRegExp); - - if (matches) { - ttl = parseInt(matches[1], 10); - } - break; - - // catch Expires and Pragma headers - case 'expires': - case 'pragma': - // and Varnish specific headers - case 'x-pass-expires': - case 'x-pass-cache-control': - phantomas.incrMetric('oldCachingHeaders'); // @desc number of responses with old, HTTP 1.0 caching headers (Expires and Pragma) - phantomas.addOffender('oldCachingHeaders', url + ' - ' + headerName + ': ' + value); - headerDate = Date.parse(value); - if (headerDate) ttl = Math.round((headerDate - now) / 1000); - break; - } - } - - //console.log(JSON.stringify(headers)); console.log("TTL: " + ttl + ' s'); - - return ttl; - } - - phantomas.setMetric('cachingNotSpecified'); // @desc number of responses with no caching header sent (no Cache-Control header) - phantomas.setMetric('cachingTooShort'); // @desc number of responses with too short (less than a week) caching time - phantomas.setMetric('cachingDisabled'); // @desc number of responses with caching disabled (max-age=0) - - phantomas.setMetric('oldCachingHeaders'); - - phantomas.on('recv', function(entry, res) { - var ttl = getCachingTime(entry.url, entry.headers); - - // static assets - if (entry.isImage || entry.isJS || entry.isCSS) { - if (ttl === false) { - phantomas.incrMetric('cachingNotSpecified'); - phantomas.addOffender('cachingNotSpecified', entry.url); - } else if (ttl <= 0) { - phantomas.incrMetric('cachingDisabled'); - phantomas.addOffender('cachingDisabled', entry.url); - } else if (ttl < 7 * 86400) { - phantomas.incrMetric('cachingTooShort'); - phantomas.addOffender('cachingTooShort', entry.url + ' cached for ' + ttl + ' s'); - } - } - }); -}; diff --git a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js index bb7762a..f2403a8 100644 --- a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js @@ -17,7 +17,7 @@ exports.module = function(phantomas) { phantomas.setMetric('jQueryOnDOMReadyFunctions'); // @desc number of functions bound to onDOMReady event phantomas.setMetric('jQueryWindowOnLoadFunctions'); // @desc number of functions bound to windowOnLoad event phantomas.setMetric('jQuerySizzleCalls'); // @desc number of calls to Sizzle (including those that will be resolved using querySelectorAll) - //phantomas.setMetric('jQueryEventTriggers'); // @desc number of jQuery event triggers + phantomas.setMetric('jQueryEventTriggers'); // @desc number of jQuery event triggers var jQueryFunctions = [ // DOM manipulations @@ -245,7 +245,7 @@ exports.module = function(phantomas) { }); - /*if (!jQuery.event) { + if (!jQuery.event) { phantomas.spy(jQuery.event, 'trigger', function(ev, data, elem) { var path = phantomas.getDOMPath(elem), type = ev.type || ev; @@ -255,7 +255,7 @@ exports.module = function(phantomas) { phantomas.incrMetric('jQueryEventTriggers'); phantomas.addOffender('jQueryEventTriggers', '"%s" on "%s"', type, path); }); - }*/ + } // jQuery events bound to window' onLoad event (#451) phantomas.spy(jQuery.fn, 'on', function(eventName, func) { diff --git a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js b/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js index ae459f9..b1b06ce 100644 --- a/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/javaScriptBottleYLT/javaScriptBottleYLT.js @@ -4,44 +4,56 @@ * @see http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ * @see http://www.quirksmode.org/blog/archives/2005/06/three_javascrip_1.html * @see http://www.stevesouders.com/blog/2012/04/10/dont-docwrite-scripts/ + * + * Run phantomas with --spy-eval to count eval() calls (see issue #467) */ /* global document: true, window: true */ -exports.version = '0.1.a'; +exports.version = '0.2'; exports.module = function(phantomas) { 'use strict'; + + phantomas.setMetric('documentWriteCalls'); //@desc number of calls to either document.write or document.writeln @offenders + phantomas.setMetric('evalCalls'); // @desc number of calls to eval (either direct or via setTimeout / setInterval) @offenders - phantomas.setMetric('documentWriteCalls'); //@desc number of calls to either document.write or document.writeln - phantomas.setMetric('evalCalls'); // @desc number of calls to eval (either direct or via setTimeout / setInterval) + // spy calls to eval only when requested (issue #467) + var spyEval = phantomas.getParam('spy-eval') === true; + if (!spyEval) { + phantomas.log('javaScriptBottlenecks: to spy calls to eval() run phantomas with --spy-eval option'); + } phantomas.once('init', function() { - phantomas.evaluate(function() { + phantomas.evaluate(function(spyEval) { (function(phantomas) { function report(msg, caller, backtrace, metric) { phantomas.log(msg + ': from ' + caller + '!'); phantomas.log('Backtrace: ' + backtrace); + phantomas.incrMetric(metric); + phantomas.addOffender(metric, "%s from %s", msg, caller); } // spy calls to eval() - /*phantomas.spy(window, 'eval', function(code) { - report('eval() called directly', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - phantomas.log('eval\'ed code: ' + (code || '').substring(0, 150) + '(...)'); - });*/ + if (spyEval) { + phantomas.spy(window, 'eval', function(code) { + report('eval() called directly', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); + phantomas.log('eval\'ed code: ' + (code || '').substring(0, 150) + '(...)'); + }); + } // spy calls to setTimeout / setInterval with string passed instead of a function - /*phantomas.spy(window, 'setTimeout', function(fn, interval) { + phantomas.spy(window, 'setTimeout', function(fn, interval) { if (typeof fn !== 'string') return; report('eval() called via setTimeout("' + fn + '")', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - });*/ + }); - /*phantomas.spy(window, 'setInterval', function(fn, interval) { + phantomas.spy(window, 'setInterval', function(fn, interval) { if (typeof fn !== 'string') return; report('eval() called via setInterval("' + fn + '")', phantomas.getCaller(), phantomas.getBacktrace(), 'evalCalls'); - });*/ + }); // spy document.write(ln) phantomas.spy(document, 'write', function(arg) { @@ -52,6 +64,6 @@ exports.module = function(phantomas) { report('document.writeln() used', phantomas.getCaller(), phantomas.getBacktrace(), 'documentWriteCalls'); }); })(window.__phantomas); - }); + }, spyEval); }); }; diff --git a/lib/tools/phantomas/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js index 0202d08..a89655f 100644 --- a/lib/tools/phantomas/phantomasWrapper.js +++ b/lib/tools/phantomas/phantomasWrapper.js @@ -29,14 +29,12 @@ var PhantomasWrapper = function() { 'js-deep-analysis': task.options.jsDeepAnalysis || false, 'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36', 'screenshot': task.options.screenshot || false, - 'viewport': '1366x768', // Mandatory 'reporter': 'json:pretty', 'analyze-css': true, 'skip-modules': [ 'blockDomains', // not needed - 'caching', // overriden 'domMutations', // not compatible with webkit 'domQueries', // overriden 'eventListeners', // overridden diff --git a/package.json b/package.json index 6b29c50..22b4096 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "express": "~4.10.6", "lwip": "0.0.6", "meow": "^3.0.0", - "phantomas": "1.9.0", + "phantomas": "1.10.0", "ps-node": "0.0.3", "q": "~1.1.2", "rimraf": "~2.2.8", diff --git a/test/core/indexTest.js b/test/core/indexTest.js index 93b0824..757c1aa 100644 --- a/test/core/indexTest.js +++ b/test/core/indexTest.js @@ -54,7 +54,7 @@ describe('index.js', function() { data.toolsResults.phantomas.should.have.a.property('offenders').that.is.an('object'); data.toolsResults.phantomas.offenders.should.have.a.property('DOMelementMaxDepth'); data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(1); - data.toolsResults.phantomas.offenders.DOMelementMaxDepth[0].should.equal('body > h1[1]'); + data.toolsResults.phantomas.offenders.DOMelementMaxDepth[0].should.equal('body > h1[0]'); // Test rules data.should.have.a.property('rules').that.is.an('object'); @@ -79,7 +79,7 @@ describe('index.js', function() { "count": 1, "tree": { "body": { - "h1[1]": 1 + "h1[0]": 1 } } }