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
}
}
}