From 9231b0914be3bf6f0d060f9b467238ad2b6bb211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 28 Apr 2015 08:03:45 +0200 Subject: [PATCH 01/32] Forgotten console.log --- front/src/js/controllers/indexCtrl.js | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/js/controllers/indexCtrl.js b/front/src/js/controllers/indexCtrl.js index 230bac7..51e4741 100644 --- a/front/src/js/controllers/indexCtrl.js +++ b/front/src/js/controllers/indexCtrl.js @@ -3,7 +3,6 @@ var indexCtrl = angular.module('indexCtrl', []); indexCtrl.controller('IndexCtrl', ['$scope', 'Settings', 'API', function($scope, Settings, API) { $scope.settings = Settings.getMergedSettings(); - console.log($scope.settings); $scope.launchTest = function() { if ($scope.url) { From b2f6f68d1abbea98766eb9894ca8001c60152352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 30 Apr 2015 22:18:27 +0200 Subject: [PATCH 02/32] Add new options to the front-end's home page --- front/src/css/icons.css | 2 +- front/src/css/index.css | 94 ++++++++++++++++++- front/src/css/main.css | 2 +- front/src/js/services/apiService.js | 19 +++- front/src/js/services/settingsService.js | 3 +- front/src/less/icons.less | 2 +- front/src/less/index.less | 109 ++++++++++++++++++++++- front/src/views/index.html | 66 +++++++++++++- 8 files changed, 282 insertions(+), 15 deletions(-) diff --git a/front/src/css/icons.css b/front/src/css/icons.css index 1d55bd0..fab4f27 100644 --- a/front/src/css/icons.css +++ b/front/src/css/icons.css @@ -1,6 +1,6 @@ @font-face { font-family: "icons"; - src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); + src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); font-weight: normal; font-style: normal; } diff --git a/front/src/css/index.css b/front/src/css/index.css index 3548240..cd85516 100644 --- a/front/src/css/index.css +++ b/front/src/css/index.css @@ -13,11 +13,11 @@ width: 50%; } .launchBtn { - background: #e74c3c; + background: #ffa319; color: #fff; } .launchBtn:focus { - background: #ffa319; + background: #e74c3c; } .launchBtn.disabled { background: #deaca6; @@ -25,6 +25,18 @@ .launchBtn.disabled:focus { color: #ddd; } +.settings { + width: 50%; + margin: 0 auto; +} +.settings input, +.settings select { + font-size: 1em; +} +.settings input[type=text] { + width: 100%; + min-width: 4em; +} .device { margin-top: 3em; } @@ -53,10 +65,86 @@ margin: 0.2em 0 0.1em; font-size: 3em; } +.settingsTooltip { + position: relative; +} +.settingsTooltip span { + font-size: 0.8em; + vertical-align: text-top; +} +.settingsTooltip div { + display: none; + position: absolute; + padding: 0.5em; + width: 25em; + background: #FFF; + color: #000; + font-size: 0.8em; + border-radius: 1em; + border: 2px solid #ffa319; + white-space: normal; + z-index: 2; +} +.settingsTooltip:hover div { + display: block; +} +.showAdvanced { + display: inline-block; + margin-top: 2em; + color: #FFF; + text-decoration: none; + font-size: 0.9em; +} +.showAdvanced:hover { + color: #ffa319; +} +.currentSettings { + font-size: 0.9em; +} +.currentSettings span { + color: #ffa319; +} +.currentSettings span:after { + color: #FFF; + content: ","; +} +.currentSettings span:last-child:after { + content: ""; +} +.advanced { + margin: 1em 0 0; + display: table; + width: 100%; + text-align: left; + border-spacing: 0.75em; +} +.advanced > div { + display: table-row; +} +.advanced > div > div { + display: table-cell; + width: 75%; +} +.advanced > div > div.label { + width: 25%; + white-space: nowrap; +} +.advanced .subTable { + display: table; + border-spacing: 0; + width: 100%; +} +.advanced .subTable > div { + display: table-row; +} +.advanced .subTable > div > div { + display: table-cell; + padding: 0 0 0.75em; +} .features { display: table; width: 50%; - margin: 8em auto 0; + margin: 6em auto 0; font-size: 0.9em; color: #413; } diff --git a/front/src/css/main.css b/front/src/css/main.css index fc20382..bc9cb47 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -1,6 +1,6 @@ @font-face { font-family: "icons"; - src: url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAABF0AA4AAAAAK9AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEMAAABWQLtM2mNtYXAAAAGIAAAAOgAAAUriHBC2Y3Z0IAAAAcQAAAAUAAAAIv+YA5pmcGdtAAAB2AAABacAAAuX2BTb8Gdhc3AAAAeAAAAACAAAAAgAAAAQZ2x5ZgAAB4gAAAdJAAAZuvXTIk1oZWFkAAAO1AAAADIAAAA2A+f0EGhoZWEAAA8IAAAAIAAAACQD/QHAaG10eAAADygAAAAPAAAALBYAAABsb2NhAAAPOAAAABgAAAAYH8gkR21heHAAAA9QAAAAIAAAACACfQyHbmFtZQAAD3AAAAEdAAAB8hTbwvhwb3N0AAAQkAAAAGIAAAB/4g/aQ3ByZXAAABD0AAAAfQAAAIqMht7EeJxjYGRiYJzAwMrAwejCmMbAwOAOpb8ySDK0MDAwMbAyM2AFAWmuKQwOHxk/cjEe+H+AQY/xIIMDUJgRJAcAixQLGgB4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGD5y/f8PUvCREUTzM0DVAwEjG8OIBwB5MwbBAAB4nGNgwAA8EMh48P8BEAYAFnAFM3icrVb5c9NGFJZ8JE5CjpKDFvVYsXGa2iuTUggGTAiSZRfcw7laCUorxU56H9Ayw9/gv+bJtDP0N/60fm9lm0CSdoZpJqP37e6nffeTyVCCjL3AD4VoPTNmtlo0tnMvoMsWrYbRoejtBZQpxn8XjILR6ch9y7bJCMnwZL1vmIYXuQ6ZikR06FBGia6g523Krdzrr5qTnt/xt+8HtrStXiCo3Q5s2gwtQVVG1TAUSUqKu7SKrcFK0BqfrzHzeTsQMKIXC5psBxF2BJ9NMlpntB5ZURiGFpnlMJRktIODMHQoqwTuyRVjGJT32gHlpUtj0oX5IZmRQzklYZfoJvl9V/BJqpyfOPc7lC3Z2PdET/Rwd7KWL8KtrSBqW/F2GMgQp5s7AY4sdmqg2aG8onGv3DcyaWjGsJSuRIilG1Nm/5DMDu6nfMmhcSXYyCmv8yxn7Au+gTajkClRXRtZUP3xKcPz3ZI9CvaEejn4k+ktZhkmePA4En5PxpwIHSnD4miSsGDk0ErKFmVcT1VMnfI6LeMtw3rh2tGXzijtUH9qMusHtiXtsGQ7NK2STManblx3aEaBKASd8e7y6wDSDWmaV9tYTWPl0CyumdMhEYhAB3ppxotELxI0g6A5NKdau0GS69bDZZo+kE8cekO1toLWTrpp2dif1/tnVWLMentBMjuL/MUuzZa5SFG6bnKGH9N4kLmETGSL7SDh4MFbt4f0stqSLfHaEFvpOb+C2uedEJ40YX8Tuy+n6pQEJoYxLxEtj4yNvmmaOlfzykiMjL8b0Kx0hU9TKMpJKI5cEUH9X3NzpjFjuG4vSs6Olelx2bqAMC3At/myQ4sqMVkuIc4sz6kky/JNleRYvqWSPMvzKhljaalknOXbKimwfEclEyw/UKJC5gOHSho8dKiswSOH3lUGTZdfw8b3YOO7uFvARpY2bGR5ATaylLCR5TJsZFmEjSxXYCPL92Ejy1XYyFIpUdOl5iionYuExyZ4Oh1oH8X1VlHklMlBJ11EETfFKZmQcVXyGPtXBkrJobVReswlulhK8uaiH2AMsYMfHo3M8eNLSlzR9n4EnukfV4IOO1E57xtLfxr8V9+Q1eSSuQiPLsN/GHyyvSjsuOrQFVU5V3No/b+oKMIO6FeREmOpKCqiyc2LUN7p9ZqyiW4PMNYxFtHR66a5uAD9VUyZJTQI/jWFJrzyQa8ihaj1cNe1F8eikt5BOdwJlqCI+31zK3iaEVlhPc2sZM+HLs/AAqap1GzZQPd5r7ZSxHMoHfYZL+pKynpxF8cZL7aAI55Br74TwyQMZtlADiU0NOAXhNaC+05QItNpl0ODI/Z5FFT+2K24kT0qaiPwbKdT7oUupPw6x0BgJ78yiIGsITQ39DYV0DxCNGSTlXG2ajpk7MAgosZuUBE1fBvZ4sGmYFuGIR8rYnXn6Nc3TdRJFTzIjOQyvjmwwBumJuLP86suDlO5oaSocNQaGMy1sJJUzAU04K3Rdvvo9ubL7BM5txVVyyde6iq6Vu5BMRcLrD3OQVoqVAHVG1XYMLpcXBKlXkGTpNfVMTQww1+jFJv/V/Wx+TxfahIj5Ei+7XBgo8/BGPrfYP9tOQjAwI+Ry024vJg2J77u6MP5Cl1GL358yv4dzFxzYZ6uAN9VdBWixVHzEVfRwKdsGKdPFJcjtQA/VX3MGYDPAEwGn6u+qXfaAHpnizk+wDZzGOwwh8EucxjsMec2wBfMYfAlcxgEzGEQMscDuMccBveZw+Ar5jB4wJwGwNfMYfANcxhEzGEQM8cF2GcOgw5zGHSZw+BA0fVRmA95QRtA32p0C+g7XU9YbGLxvaIbI/YPvNDsHzVi9k8aMfVnRbUR9RdeaOqvGjH1N42Y+lDRzRH1ES809XeNmPqHRkx9rJ5O5DLDH09umQoHlF1uPxl+U5x/ACtxTWMAAAEAAf//AA94nM1YS2/b2BU+h9TDki3ZoihST4oPvaz3W35FZuokflBOMrHsyJU9FZ0iMRBkYBSD2VZdFl1MBkUX3aRZtNNi4EWAme5VoP9iNt500V2Bbtou5F5KViczKJxmTBc2ee+1uNA5upffd77vgARwPqAAB4BAgxXs4IBpcMEseMALPuAhACGIQBQkUCAOSZiHDOSgACWoQA0asAjLsA478Bz/rr3JPOyof0giLVESfQzBkCgGjwFAokF6AlQKrRbKqstomUe7zWLXwZZGx5TNocNUBqedU9M6OLPomnG6dAVncjjrnpnVwR3LU545t+cJzBXQy8x5dWCK6GMZnw5sCXmO5XXg4mUq4OcCT8BfwVDQH9LrKFYxEhYjRwlEFLo1FASXBuFwtAvRqDvaCqlfXKRKnRi5hoLiyY1Ndl/95ebGrZWlpYWFer1aLZeLxXw+m02nU6lEIhaTZVEUhGdPj3qH3cd7D7Y3djZ31m6vrN9aX1peWl5eWlhcWFxcqDfqjUa9WqvWatVypVyplIulYqlUzBfyhUI+m8vmctl0Jp3JpFPzqfn5VCKZSCYTsXgsHo/JiqwosiiJkiQKUSEaFRo2NuP1XHahZL3kQgF5N9rzmGyit4kN8g/5KGCyUS9z7CxWyGzLGFtmkwuYqIrf/huKFAzh+41zePGSpl++GM+dTyjqk85oXu07g17H1pduhnF/ueXwBp19Bw6+b5gh4Nm3k8az/0R98XJYmYQl81/6RrRNJsxsGimQPMi7SDBnBj5XYY1gVIMHAFc5sLjkkxoVnzIZ3opHMfFQxkNV1d7kNnPnVXXQOzvr91EdDgBEU/Z1B/axrJ53dlu3LFN23IohAkUjpceRBouVtugJtILNbrXpSbTDlMM+pafQAc5ph1Ofx2mYcU3P6Gl0gXvW5dYzOAtzntk5PYseYLweRq8oOdoLrM/L6nn0Acf7OL2APPgDvF8vYoDQViColzAE4UgorMuiQAOMKMOllTESkbogSW6p1W5fThzt/fb+471HD7TNe2tq8z0pQ82/B2lUr0gaSbpWKderiQzKNpZbRh9t43ipifFGreLjTXwb5VdzdofD/tqY5l6FYjVUa7Fh/5n6zMy38j7anzvcDnI/H/6z0ERs/uz0449PIWYy7neuhnw0EE/Qb//Oair6+/2BAc7Bd1Zz6bffU3sXw+BXc3igBYf4D1XcRJt17QeU09H9IcV6H6Kfb2MwYN1qobCxg8q69oYl+ugVWGgrZTkmZEFRuBtFirCFhaL/O1eI+D+ThZyj3s0VFalEX7AF+flCFwydYVCF0gVFcStEFf3GSJC2WE9uaIb76i/ub1/OZ529Rx9sH94/1LbW7743n/0f2YzOo0I0D0/UT5nj7ZUayolkQ/E1BKyQZ0QPmYgwTTmstX8aWeF+tJq6G9oYnMNo+eaxqUj7lZLC7ha/cAeF4MZgqBqzNnkGsim4M5itAwdX4zbaOAEP0aLGjnvyaHCbV05UDRFqa0geM0nu5RkbQgyx4+XP/b5KhGaYYX5yTvbBXKIjUYa9cTR8zYb4ueGGESnM9JAwnzn7v0pO4ACOKac6vYGuOzi7jh56U3uTIjT3RQpphVJGNjAWM2xgAxUaFGKt5sfWCizpsbOqixTR+RfeSs5SF+YqNzZXMJOfeKsCGtZKhzmpSF14q9LYWwnIlifmKloZmSsd/NWxuQJSvCPhWOQoSbxVvLuA8fjIWyW6kEi4E4TyTi+SHRnBWCgYO7nB6e6rv/7wcLt1W72cA398dHj84fFuu3WwfbC1ee+uqt3WVpvEQl4bGy5ekQ3jBvLKXATtSq1RI0N2U6xAlZtUNU8l33KLZsqOM1XtuxmiCF730tpKNLqitcdL+i2LaCY0+yqKjLuniuR6+E04Yym+ZQ7N0iaG/rsSP9Y8koccjE8iJ0KbS4dDOOsh9M6wP+yb6vWMLxzRoHoOYE6PS4M2dPCvY3L7HYSliBQ+JrJEFCNPiVBS0FTjl8N3CyW5RIVACoekJyOdVBm1jKoEHNGDSYPr9+M8IydGolJE/OiGZrqvfrrdar6ju9V5vLf7wcNWe7t9b21Fa2rXR2S1q8o66UJSSGNJQV+TnDgZDi4qvDpav7IbTtLx8+EZiteqJoaesWktYp9oifN/EXzZ8E9XwldzhLBD/NsfHz2gXLg5Nku/LSLPcj7+GDhSJJ8CJJCmgNaBSk6qcmrSnp2f1OT0uD1bkyhnZlKUs5OinJsUZTk/Kco+H7MbK1A+YHkfq5cw4PcH9hT0l0f1+EjAYIUU5GCEYAYx2q2i0V4hBVnsgii6RYKzz0dpsjx3QvJkucBHNzPRffWz7Za6ejnMDrqdx2OYrd9Z1VSteev6gFa/aguZlugy7+VsdimPDW+dL3M+u5Ks1u0sZ3SJZJuJaDuHU+ycelxfzzDB4f0gM/O1y+NmnFkn42GCDLlNhdxnw09xwA1bvIAo8PgVNyCioedmBi4jFPNv0bcgRQAAAHicY2BkYGAA4tkfYrri+W2+MnAzMYDAxZSEixA66OD/X/8PMLExHgRyORjA0gBqPw1aAAB4nGNgZGBgPPD/AIMeEwMDw/8vTDwMQBEUwA0Ae20Ev3icY2JgYGAiEgMAAjwAFwAAAAAAAdACfAPMBIAGPAb8CM4JcgsiDN0AAQAAAAsAbAAkAAAAAAACAHIAggB3AAABZAuXAAAAAHicXY69TsMwFIVP+odoEAIhMZulC1L6M/YB2pkO2dPESVslceS4lSoxM/MUzDwFz8WJeyUqbOn6O+ceXxvAA34QoFsBhr52q4cbqgv3SXfCA/Kj8BAhnoVHVC/CY7xiIhziCW+cEAxu6YyRCfdwj1q4T/9deED+EB5y+qfwiP6X8BgxvoVDTILRPjV1u9HFsUysZ19ibdu9qdU8mnm91rW2idOZ2p5VeyoWzuUqt6ZSK1M7XZZGNdYcdOqinXPNcjrNxY9SU2GPFIZ/brGBRoEjSiSwV/4fxUxY73RaYY4Is6v+mv3aZxI4nhkzW5xZW5w4e0HXIafOmTGoSCt/t0uX3IZO43sHOin9CDt/q8ESU+78Xz7yr1e/MPVTYgAAAHicbcFBDsIwDARAb5umLXkLJ17kIFNFMjE4Rv0+B67M0EQ/C/1XiDBhRsKCjBUbdlxQCrvbeVV5xC1V9jEr16RtRFKzV35abSrb+yMjmvU87i7Sc3BVifVk760fRF+VyBj+AAB4nGPw3sFwIihiIyNjX+QGxp0cDBwMyQUbGdicNjEwMmiBGJu5mRg5ICx+BjCL3WkXMwNQmhPI5nDaxeAAYTMzuGxUYewIjNjg0BGxkTnFZaMaiLeLo4GBkcWhIzkkAqQkEgg28zIx8mjtYPzfuoGldyMTUB9rigsAZa0kmwAAAA==") format("woff"); + src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); font-weight: normal; font-style: normal; } diff --git a/front/src/js/services/apiService.js b/front/src/js/services/apiService.js index 4aa145f..ed5dc9d 100644 --- a/front/src/js/services/apiService.js +++ b/front/src/js/services/apiService.js @@ -5,13 +5,28 @@ apiService.factory('API', ['$location', 'Runs', 'Results', function($location, R return { launchTest: function(url, settings) { - Runs.save({ + var runObject = { url: url, waitForResponse: false, screenshot: true, jsTimeline: true, device: settings.device - }, function(data) { + }; + + if (settings.waitForSelector && settings.waitForSelector !== '') { + runObject.waitForSelector = settings.waitForSelector; + } + + if (settings.cookie && settings.cookie !== '') { + runObject.cookie = settings.cookie; + } + + if (settings.authUser && settings.authUser !== '' && settings.authPass && settings.authPass !== '') { + runObject.authUser = settings.authUser; + runObject.authPass = settings.authPass; + } + + Runs.save(runObject, function(data) { $location.path('/queue/' + data.runId); }, function(response) { if (response.status === 429) { diff --git a/front/src/js/services/settingsService.js b/front/src/js/services/settingsService.js index 6ecad61..81f9f5b 100644 --- a/front/src/js/services/settingsService.js +++ b/front/src/js/services/settingsService.js @@ -6,7 +6,8 @@ settingsService.factory('Settings', ['localStorageService', function(localStorag getMergedSettings: function() { var defaultSettings = { - device: 'desktop' + device: 'desktop', + showAdvanced: false }; var savedValues = localStorageService.get('settings'); diff --git a/front/src/less/icons.less b/front/src/less/icons.less index cd851ca..40b7108 100644 --- a/front/src/less/icons.less +++ b/front/src/less/icons.less @@ -4,7 +4,7 @@ @font-face { font-family:"icons"; - src:url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); + src:url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"); font-weight:normal; font-style:normal; } diff --git a/front/src/less/index.less b/front/src/less/index.less index 553b4e5..a7efe86 100644 --- a/front/src/less/index.less +++ b/front/src/less/index.less @@ -16,10 +16,10 @@ } .launchBtn { - background: #e74c3c; + background: #ffa319; color: #fff; &:focus { - background: #ffa319; + background: #e74c3c; } &.disabled { background: #deaca6; @@ -30,6 +30,20 @@ } +.settings { + width: 50%; + margin: 0 auto; + + input, select { + font-size: 1em; + } + + input[type=text] { + width: 100%; + min-width: 4em; + } +} + .device { margin-top: 3em; .item { @@ -62,10 +76,99 @@ } } +.settingsTooltip { + position: relative; + span { + font-size: 0.8em; + vertical-align: text-top; + } + + div { + display: none; + position: absolute; + padding: 0.5em; + width: 25em; + background: #FFF; + color: #000; + font-size: 0.8em; + border-radius: 1em; + border: 2px solid #ffa319; + white-space: normal; + z-index: 2; + } + + &:hover div { + display: block; + } +} + +.showAdvanced { + display: inline-block; + margin-top: 2em; + color: #FFF; + text-decoration: none; + font-size: 0.9em; + + &:hover { + color: #ffa319; + } +} + +.currentSettings { + font-size: 0.9em; + + span { + color: #ffa319; + &:after { + color: #FFF; + content: ","; + } + + &:last-child:after { + content: ""; + } + } +} + +.advanced { + margin: 1em 0 0; + display: table; + width: 100%; + text-align: left; + border-spacing: 0.75em; + + > div { + display: table-row; + + > div { + display: table-cell; + width: 75%; + + &.label { + width: 25%; + white-space: nowrap; + } + } + } + + .subTable { + display: table; + border-spacing: 0; + width: 100%; + > div { + display: table-row; + > div { + display: table-cell; + padding: 0 0 0.75em; + } + } + } +} + .features { display: table; width: 50%; - margin: 8em auto 0; + margin: 6em auto 0; font-size: 0.9em; color: #413; diff --git a/front/src/views/index.html b/front/src/views/index.html index 02caf53..fbea1b2 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -7,9 +7,69 @@
Choose the simulated device:
-
Desktop
-
Tablet
-
Phone
+
Desktop
+
Tablet
+
Phone
+
+ [ + Advanced settings  ✚ + Hide advanced settings  ✖ + ] + + Currently set: + wait for selector + cookie + authentication + +
+
+
+ Wait selector + + +
Wait for a CSS selector

Once the page is considered loaded, PhantomJS will repeatedly try to match the given CSS selector until it is found in the page. A 60 seconds timeout still applies anyway.

Example: "body.loaded"
+
+
+
+
+
+
+ Cookie + + +
Cookie

Adds a cookie on the main domain.

Example: "bar=foo;domain=url"
+
+
+
+
+
+
+ Authent + + +
Basic HTTP authentication

Enter your credentials here if you need to bypass a basic authentication.

PS: if your authentication is not basic, you might be able to copy the session cookie from your browser, paste it in the "Cookie" setting and launch a run before your cookie expires.
+
+
+
+
+
username
+
+
+
+
password
+
+
+
+
+
From bb2f0e6c55f80e638f26f36ba7fb0418597ca987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 4 May 2015 15:24:00 +0200 Subject: [PATCH 03/32] Add a cookie advanced option on the front page --- bin/cli.js | 14 ++++++++++++++ front/src/js/services/apiService.js | 6 +++++- front/src/views/index.html | 8 ++++---- lib/server/controllers/apiController.js | 12 ++++++++++-- lib/tools/phantomas/phantomasWrapper.js | 5 ++++- test/api/apiTest.js | 12 ++++++++++-- 6 files changed, 47 insertions(+), 10 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index b22da78..7d4d4a0 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -15,6 +15,10 @@ var cli = meow({ ' --device Use "phone" or "tablet" to simulate a mobile device (by user-agent and viewport size).', ' --screenshot Will take a screenshot and use this value as the output path. It needs to end with ".png".', ' --js-deep-analysis When activated, the javascriptExecutionTree will contain sub-requests.', + ' --wait-for-selector Once the page is loaded, Phantomas will wait until the given CSS selector matches some elements.', + ' --cookie Adds a cookie on the main domain.', + ' --auth-user Basic HTTP authentication username.', + ' --auth-pass Basic HTTP authentication password.', '' ].join('\n'), pkg: '../package.json' @@ -54,6 +58,16 @@ if (cli.flags.jsDeepAnalysis === true || cli.flags.jsDeepAnalysis === 'true') { // Device simulation options.device = cli.flags.device || 'desktop'; +// Wait for CSS selector +options.waitForSelector = cli.flags.waitForSelector || null; + +// Cookie +options.cookie = cli.flags.cookie || null; + +// HTTP basic auth +options.authUser = cli.flags.authUser || null; +options.authPass = cli.flags.authPass || null; + (function execute(url, options) { 'use strict'; diff --git a/front/src/js/services/apiService.js b/front/src/js/services/apiService.js index ed5dc9d..c0234bc 100644 --- a/front/src/js/services/apiService.js +++ b/front/src/js/services/apiService.js @@ -10,7 +10,11 @@ apiService.factory('API', ['$location', 'Runs', 'Results', function($location, R waitForResponse: false, screenshot: true, jsTimeline: true, - device: settings.device + device: settings.device, + waitForSelector: settings.waitForSelector, + cookie: settings.cookie, + authUser: settings.authUser, + authPass: settings.authPass, }; if (settings.waitForSelector && settings.waitForSelector !== '') { diff --git a/front/src/views/index.html b/front/src/views/index.html index fbea1b2..aff58e1 100644 --- a/front/src/views/index.html +++ b/front/src/views/index.html @@ -22,7 +22,7 @@ authentication
-
+
Cookie @@ -42,7 +42,7 @@
-
+
+
Type
+
Params
+
+
+ +
+ +

This is the colors palette, sized by total occurrences:

diff --git a/front/src/views/timeline.html b/front/src/views/timeline.html index dc0398c..497340e 100644 --- a/front/src/views/timeline.html +++ b/front/src/views/timeline.html @@ -7,8 +7,8 @@
-

Javascript Timeline

-

This graph gives a quick view of when the Javascript interactions with the DOM occur during the loading of the page.

+

JavaScript Timeline

+

This graph gives a quick view of when the JavaScript interactions with the DOM occur during the loading of the page.

@@ -45,7 +45,7 @@
Page is complete
-
Executing Javascript and DOM queries here is a bad practice and slows down the DOM construction.
+
Executing JavaScript and DOM queries here is a bad practice and slows down the DOM construction.
Some frameworks do things here, but it's not reliable and should be avoided.
Also known as "document ready". This is where you should execute top-priority scripts, like binding action buttons or launch a video player.
Here you can execute mid-priority tasks. Loading a script with createElement('script') is one way to do so.
@@ -54,9 +54,9 @@
-

Javascript Profiler

+

JavaScript Profiler

- The table below shows the interactions between Javascript and the DOM. It is useful to understand what happens while the page loads. + The table below shows the interactions between the JavaScript and the DOM. It is useful to understand what happens while the page loads.

diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index abdd706..4bc9347 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -66,6 +66,15 @@ var policies = { }; } }, + "DOMaccesses": { + "tool": "jsExecutionTransformer", + "label": "DOM access", + "message": "

TODO

TODO

", + "isOkThreshold": 50, + "isBadThreshold": 2000, + "isAbnormalThreshold": 3000, + "hasOffenders": false + }, "DOMinserts": { "tool": "phantomas", "label": "DOM inserts", @@ -195,6 +204,48 @@ var policies = { }; } }, + "eventsScrollBound": { + "tool": "phantomas", + "label": "Scroll events bound", + "message": "

Number of 'scroll' event listeners binded to 'window' or 'document'.

Asking too much work to the browser on scroll hurts the smoothness of the scroll. Merging all your event listeners into an unique listener can help you factorize their code and reduce their footprint on scroll.

", + "isOkThreshold": 1, + "isBadThreshold": 7, + "isAbnormalThreshold": 12, + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^bound by (.*)$/.exec(offender); + + if (!parts) { + debug('eventsScrollBound offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } + + var backtraceArray = offendersHelpers.backtraceToArray(parts[1]); + + return { + backtrace: backtraceArray || [] + }; + }) + }; + } + }, + "DOMaccessesOnScroll": { + "tool": "jsExecutionTransformer", + "label": "DOM access on scroll", + "message": "

This rule counts the number of DOM-accessing functions calls, such as queries, readings, writings, bindings and jQuery functions.

Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.

One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.

Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use throttling or deboucing.

", + "isOkThreshold": 1, + "isBadThreshold": 10, + "isAbnormalThreshold": 20, + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + return offenders; + } + }, "jsErrors": { "tool": "phantomas", "label": "JavaScript errors", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 319c83c..aeda814 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -12,6 +12,7 @@ "domManipulations": { "label": "DOM manipulations", "policies": { + "DOMaccesses": 3, "DOMinserts": 2, "DOMqueries": 1, "DOMqueriesWithoutResults": 2, @@ -19,6 +20,13 @@ "eventsBound": 1 } }, + "scroll": { + "label": "Scroll bottlenecks", + "policies": { + "eventsScrollBound": 1, + "DOMaccessesOnScroll": 4 + } + }, "badJavascript": { "label": "Bad JavaScript", "policies": { @@ -99,6 +107,7 @@ "globalScore": { "domComplexity": 1, "domManipulations": 2, + "scroll": 1, "badJavascript": 1, "jQueryVersion": 1, "cssSyntaxError": 1, diff --git a/lib/offendersHelpers.js b/lib/offendersHelpers.js index 92405d9..a3630e0 100644 --- a/lib/offendersHelpers.js +++ b/lib/offendersHelpers.js @@ -115,7 +115,7 @@ var OffendersHelpers = function() { var parts = null; for (var i=0 ; i 0) { - node.data.callDetails.context.elements = node.data.callDetails.context.elements.map(offendersHelpers.domPathToDomElementObj, offendersHelpers); - } + // Transform domPaths into objects + changeListOfDomPaths(node); - if (node.data.type === 'appendChild' || node.data.type === 'insertBefore' || node.data.type === 'getComputedStyle') { - node.data.callDetails.arguments[0] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[0]); - } - - if (node.data.type === 'insertBefore') { - node.data.callDetails.arguments[1] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[1]); - } - }); + // Count the number of DOM accesses, by counting the tree leafs + metrics.DOMaccesses += countTreeLeafs(node); }); } - debug('JS execution transformation complete'); + + debug('Starting scroll execution transformation'); + scrollExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.scrollExecutionTree[0]); + if (scrollExecutionTree.children) { + scrollExecutionTree.children.forEach(function(node) { + + // Mark a event flag + if (['documentScroll', 'windowScroll', 'window.onscroll'].indexOf(node.data.type) >= 0) { + node.windowPerformance = true; + } + + // Transform domPaths into objects + changeListOfDomPaths(node); + + // Count the number of DOM accesses, by counting the tree leafs + metrics.DOMaccessesOnScroll += countTreeLeafs(node); + }); + } + debug('Scroll execution transformation complete'); + } catch(err) { throw err; } data.javascriptExecutionTree = javascriptExecutionTree; + data.toolsResults.jsExecutionTransformer = { - metrics: metrics + metrics: metrics, + offenders: { + DOMaccessesOnScroll: scrollExecutionTree + } }; return data; @@ -97,6 +111,39 @@ var jsExecutionTransformer = function() { } fn(node); } + + function changeListOfDomPaths(rootNode) { + treeRecursiveParser(rootNode, function(node) { + + if (node.data.callDetails && node.data.callDetails.context && node.data.callDetails.context.length > 0) { + node.data.callDetails.context.elements = node.data.callDetails.context.elements.map(offendersHelpers.domPathToDomElementObj, offendersHelpers); + } + + if (node.data.type === 'appendChild' || node.data.type === 'insertBefore' || node.data.type === 'getComputedStyle') { + node.data.callDetails.arguments[0] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[0]); + } + + if (node.data.type === 'insertBefore') { + node.data.callDetails.arguments[1] = offendersHelpers.domPathToDomElementObj(node.data.callDetails.arguments[1]); + } + }); + } + + // Returns the number of leafs (nodes without children) + function countTreeLeafs(rootNode) { + var count = 0; + + treeRecursiveParser(rootNode, function(node) { + if (!node.children && + !node.error && + !node.windowPerformance && + node.data.type !== 'jQuery loaded') { + count ++; + } + }); + + return count; + } }; module.exports = new jsExecutionTransformer(); \ No newline at end of file diff --git a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js index 4ec31ef..59e21a3 100644 --- a/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js +++ b/lib/tools/phantomas/custom_modules/core/scopeYLT/scopeYLT.js @@ -104,10 +104,11 @@ exports.module = function(phantomas) { var root = new ContextTreeNode(null, {type: 'main'}); var currentContext = root; + var depth = 0; + if (deepAnalysis) { phantomas.log('Entering deep Javascript analysis mode'); } - var depth = 0; // Add a child but don't enter his context function pushContext(data) { @@ -176,6 +177,13 @@ exports.module = function(phantomas) { return root; } + + // Empty the tree + function resetTree() { + root = new ContextTreeNode(null, {type: 'main'}); + currentContext = root; + depth = 0; + } function ContextTreeNode(parent, data) { @@ -196,6 +204,7 @@ exports.module = function(phantomas) { phantomas.leaveContext = leaveContext; phantomas.getContextData = getContextData; phantomas.readFullTree = readFullTree; + phantomas.resetTree = resetTree; })(); diff --git a/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js b/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js index a9772a5..697b1db 100644 --- a/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jsTreeYLT/jsTreeYLT.js @@ -9,11 +9,11 @@ exports.version = '0.1'; exports.module = function(phantomas) { 'use strict'; - phantomas.setMetric('javascriptExecutionTree'); // @desc number of duplicated DOM queries + phantomas.setMetric('javascriptExecutionTree'); // save data phantomas.on('report', function() { - phantomas.log('Reading execution tree JSON'); + phantomas.log('JS execution tree: Reading execution tree JSON'); phantomas.evaluate(function() {(function(phantomas) { var fullTree = phantomas.readFullTree(); diff --git a/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js b/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js new file mode 100644 index 0000000..cd6e1df --- /dev/null +++ b/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js @@ -0,0 +1,75 @@ +exports.version = '0.1'; + +exports.module = function(phantomas) { + 'use strict'; + + phantomas.setMetric('scrollExecutionTree'); + + phantomas.on('report', function() { + + phantomas.evaluate(function() { + (function(phantomas) { + + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent('scroll', false, false, null); + + function triggerScrollEvent() { + phantomas.resetTree(); + + try { + + // Chrome triggers them in this order: + + // 1. document + phantomas.pushContext({ + type: 'documentScroll' + }); + document.dispatchEvent(evt); + + // 2. window + phantomas.pushContext({ + type: 'windowScroll' + }); + window.dispatchEvent(evt); + + // 3. onscroll() + if (window.onscroll) { + phantomas.pushContext({ + type: 'window.onscroll' + }); + window.onscroll(); + } + + } catch(e) { + phantomas.log('ScrollListener error: %s', e); + } + } + + var firstScrollTime = Date.now(); + phantomas.log('ScrollListener: triggering a first scroll event...'); + triggerScrollEvent(); + + + // Ignore the first scroll event and only save the second one, + // because we want to detect un-throttled things, throttled ones are ok. + var secondScrollTime = Date.now(); + phantomas.log('ScrollListener: triggering a second scroll event (%dms after the first)...', secondScrollTime - firstScrollTime); + triggerScrollEvent(); + + + var fullTree = phantomas.readFullTree(); + if (fullTree !== null) { + phantomas.setMetric('scrollExecutionTree', true, true); + phantomas.addOffender('scrollExecutionTree', JSON.stringify(fullTree)); + phantomas.log('ScrollListener: scrollExecutionTree correctly extracted'); + } else { + phantomas.log('Error: scrollExecutionTree could not be extracted'); + } + + + phantomas.log('ScrollListener: end of scroll triggering'); + + })(window.__phantomas); + }); + }); +}; diff --git a/test/core/offendersHelpersTest.js b/test/core/offendersHelpersTest.js index 6bf59c9..3cbefaf 100644 --- a/test/core/offendersHelpersTest.js +++ b/test/core/offendersHelpersTest.js @@ -166,6 +166,40 @@ describe('offendersHelpers', function() { ]); }); + it('should transform another backtrace syntax into an array', function() { + var result = offendersHelpers.backtraceToArray('phantomjs://webpage.evaluate():38 / e (http://s7.addthis.com/js/300/addthis_widget.js:1) / a (http://s7.addthis.com/js/300/addthis_widget.js:1) / http://s7.addthis.com/js/300/addthis_widget.js:3 / e (http://s7.addthis.com/js/300/addthis_widget.js:1) / http://s7.addthis.com/js/300/addthis_widget.js:8'); + + result.should.deep.equal([ + { + file: 'phantomjs://webpage.evaluate()', + line: 38 + }, + { + functionName: 'e', + file: 'http://s7.addthis.com/js/300/addthis_widget.js', + line: 1 + }, + { + functionName: 'a', + file: 'http://s7.addthis.com/js/300/addthis_widget.js', + line: 1 + }, + { + file: 'http://s7.addthis.com/js/300/addthis_widget.js', + line: 3 + }, + { + functionName: 'e', + file: 'http://s7.addthis.com/js/300/addthis_widget.js', + line: 1 + }, + { + file: 'http://s7.addthis.com/js/300/addthis_widget.js', + line: 8 + } + ]); + }); + it('should return null if it fails', function() { var result = offendersHelpers.backtraceToArray('http://pouet.com/js/jquery.footer-transverse-min-v1.0.20.js:1 /http://pouet.com/js/main.js:1'); From f8f792645b720a985656d700afd97d4d867b1c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 17:10:01 +0200 Subject: [PATCH 16/32] Improve scroll bottlenecks rules --- front/src/views/rule.html | 3 +++ lib/metadata/policies.js | 9 +++++---- .../modules/eventYLT/eventYLT.js | 14 +++++++++++++- .../modules/scrollListener/scrollListener.js | 8 +------- test/www/scroll-page.html | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 test/www/scroll-page.html diff --git a/front/src/views/rule.html b/front/src/views/rule.html index a31efa4..9385be9 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -48,6 +48,9 @@
+ Scroll event bound on window + Scroll event bound on document + window.onscroll function declared
no backtrace
backtrace diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index 4bc9347..fc18c4d 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -216,7 +216,7 @@ var policies = { return { count: offenders.length, list: offenders.map(function(offender) { - var parts = /^bound by (.*)$/.exec(offender); + var parts = /^bound by (.*) on ([^ ]+)$/.exec(offender); if (!parts) { debug('eventsScrollBound offenders transform function error with "%s"', offender); @@ -228,7 +228,8 @@ var policies = { var backtraceArray = offendersHelpers.backtraceToArray(parts[1]); return { - backtrace: backtraceArray || [] + backtrace: backtraceArray || [], + target: parts[2] }; }) }; @@ -239,8 +240,8 @@ var policies = { "label": "DOM access on scroll", "message": "

This rule counts the number of DOM-accessing functions calls, such as queries, readings, writings, bindings and jQuery functions.

Two scroll events are triggered quickly, one after the other, and only the second one is analyzed so throttled functions are ignored.

One of the main reasons of a poor scrolling experience is when too much JS is executed on each scroll event. Note that some devices such as smartphones and MacBooks send more scroll events than others.

Reduce the number of DOM accesses inside scroll listeners. Put DOM queries outside them when possible. Use throttling or deboucing.

", "isOkThreshold": 1, - "isBadThreshold": 10, - "isAbnormalThreshold": 20, + "isBadThreshold": 12, + "isAbnormalThreshold": 25, "hasOffenders": true, "offendersTransformFn": function(offenders) { return offenders; diff --git a/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js b/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js index 8409fbd..82b678e 100644 --- a/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/eventYLT/eventYLT.js @@ -41,7 +41,7 @@ exports.module = function(phantomas) { // count window.addEventListener('scroll', ...) - issue #508 if (eventType === 'scroll' && (path === 'window' || path === '#document')) { phantomas.incrMetric('eventsScrollBound'); - phantomas.addOffender('eventsScrollBound', 'bound by %s', phantomas.getBacktrace()); + phantomas.addOffender('eventsScrollBound', 'bound by %s on %s', phantomas.getBacktrace(), path); } } @@ -67,4 +67,16 @@ exports.module = function(phantomas) { })(window.__phantomas); }); }); + + phantomas.on('report', function() { + phantomas.evaluate(function() { + (function(phantomas) { + // Check if a window.onscroll function is defined + if (typeof(window.onscroll) === "function") { + phantomas.incrMetric('eventsScrollBound'); + phantomas.addOffender('eventsScrollBound', 'bound by %s on %s', '', 'window.onscroll'); + } + }(window.__phantomas)); + }); + }); }; diff --git a/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js b/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js index cd6e1df..bc250b0 100644 --- a/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js +++ b/lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js @@ -32,13 +32,7 @@ exports.module = function(phantomas) { }); window.dispatchEvent(evt); - // 3. onscroll() - if (window.onscroll) { - phantomas.pushContext({ - type: 'window.onscroll' - }); - window.onscroll(); - } + // No need to call window.onscroll(), it's called by the scroll event on window } catch(e) { phantomas.log('ScrollListener error: %s', e); diff --git a/test/www/scroll-page.html b/test/www/scroll-page.html new file mode 100644 index 0000000..2ff52a5 --- /dev/null +++ b/test/www/scroll-page.html @@ -0,0 +1,18 @@ + + + Scroll page + + +

Simple page

+ + + + \ No newline at end of file From d83f150810bd37dafb5c8ab234e882e3f0a3df2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 19:13:59 +0200 Subject: [PATCH 17/32] New assetsNotGzipped rule --- front/src/views/rule.html | 5 +++++ lib/metadata/policies.js | 30 ++++++++++++++++++++++++++- lib/metadata/scoreProfileGeneric.json | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 9385be9..6ec11ab 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -150,6 +150,11 @@
+
+ + ({{offender.type}}) +
+
cached for {{offender.ttlWithUnit}} {{offender.unit}} diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index fc18c4d..75079f8 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -1130,6 +1130,35 @@ var policies = { "isAbnormalThreshold": 1, "hasOffenders": true }, + "assetsNotGzipped": { + "tool": "phantomas", + "label": "Not gzipped", + "message": "

This is the number of requests that should be compressed with gzip but aren't.

Gzip is a powerfull weight reducer and should be enabled on text-based assets in your server's configuration. Note that gzipping small files (< 1 KB) is arguable, and that some assets such as images should not be gzipped as they are already compressed. Here is a list of Content-Types that should be gzipped.

", + "isOkThreshold": 0, + "isBadThreshold": 12, + "isAbnormalThreshold": 20, + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^([^ ]*) \((.+)\)$/.exec(offender); + + if (!parts) { + debug('assetsNotGzipped offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } + + return { + file: parts[1], + type: parts[2] + }; + }) + }; + } + }, "closedConnections": { "tool": "phantomas", "label": "Connections closed", @@ -1250,7 +1279,6 @@ var policies = { domain: parts[1], requests: parseInt(parts[2]) }; - }) }; } diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index aeda814..841444e 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -95,6 +95,7 @@ "label": "Network", "policies": { "notFound": 2, + "assetsNotGzipped": 1.5, "closedConnections": 2, "multipleRequests": 2, "cachingNotSpecified": 1, From 00e2717653dd1dcc285ba5bb25be3485e4969bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 19:50:14 +0200 Subject: [PATCH 18/32] Rising scores on CSS Complexity and Bad CSS --- lib/metadata/policies.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index fc18c4d..6cd0aa7 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -487,9 +487,9 @@ var policies = { "tool": "phantomas", "label": "Rules count", "message": "

Having a huge number of CSS rules hurts performances. If the number of CSS rules is higher than the number of DOM elements, there is clearly a problem.

Huge stylesheets generally occur when the different pages of a website load all the CSS, concatenated in a single stylesheet, even if a large part of the rules are page-specific. Solution is to create one main CSS file with global rules and one custom file per page.

", - "isOkThreshold": 500, - "isBadThreshold": 2500, - "isAbnormalThreshold": 4000, + "isOkThreshold": 750, + "isBadThreshold": 3000, + "isAbnormalThreshold": 4500, "hasOffenders": false }, "cssComplexSelectors": { @@ -497,7 +497,7 @@ var policies = { "label": "Complex selectors", "message": "

Complex selectors are CSS selectors with 4 or more expressions, like \"#header ul li .foo\".

They are adding more work for the browser, and this could be avoided by simplifying selectors.

", "isOkThreshold": 0, - "isBadThreshold": 500, + "isBadThreshold": 600, "isAbnormalThreshold": 2000, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -515,8 +515,8 @@ var policies = { "label": "Complex attributes selector", "message": "

Complex attributes selectors are one of these:

  • .foo[type*=bar] (contains bar)
  • .foo[type^=bar] (starts with bar)
  • .foo[type|=bar] (starts with bar or bar-)
  • .foo[type$=bar] (ends with bar)
  • .foo[type~=bar baz] (bar or baz)

Their matching process needs more CPU and it has a cost on performances.

", "isOkThreshold": 0, - "isBadThreshold": 50, - "isAbnormalThreshold": 100, + "isBadThreshold": 75, + "isAbnormalThreshold": 150, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -600,8 +600,8 @@ var policies = { "label": "Duplicated selectors", "message": "

This is when two or more selectors are strictly identical and should be merged.

", "isOkThreshold": 0, - "isBadThreshold": 40, - "isAbnormalThreshold": 80, + "isBadThreshold": 50, + "isAbnormalThreshold": 100, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -629,8 +629,8 @@ var policies = { "label": "Duplicated properties", "message": "

This is the number of property definitions duplicated within a selector.

", "isOkThreshold": 0, - "isBadThreshold": 50, - "isAbnormalThreshold": 100, + "isBadThreshold": 60, + "isAbnormalThreshold": 120, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -663,7 +663,7 @@ var policies = { "label": "Empty rules", "message": "

Very easy to fix: remove all empty rules.

", "isOkThreshold": 0, - "isBadThreshold": 40, + "isBadThreshold": 50, "isAbnormalThreshold": 100, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -717,8 +717,8 @@ var policies = { "label": "Uses of !important", "message": "

It can be useful, but only as a last resort. It is a bad practice because it overrides the normal cascading logic. The more you use !important, the more you need it again to over-override. This conducts to a poor maintainability.

", "isOkThreshold": 0, - "isBadThreshold": 50, - "isAbnormalThreshold": 150, + "isBadThreshold": 75, + "isAbnormalThreshold": 200, "hasOffenders": true, "offendersTransformFn": function(offenders) { return { @@ -752,7 +752,7 @@ var policies = { "label": "Old IE fixes", "message": "

What browser do you need to support? Once you've got the answer, take a look at these old rules that pollute your CSS code and remove them.

IE6:

  • * html
  • html > body (everything but IE6)

IE7:

  • *height: 123px;
  • height: 123px !ie;

IE9:

  • -ms-filter
  • progid:DXImageTransform.Microsoft

", "isOkThreshold": 0, - "isBadThreshold": 50, + "isBadThreshold": 75, "isAbnormalThreshold": 300, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -809,7 +809,7 @@ var policies = { "label": "Old prefixes", "message": "

Many property prefixes such as -moz- or -webkit- are not needed anymore, or by very few people. You can remove them or replace them with the non-prefixed version. This will help reducing your stylesheets weight.

", "isOkThreshold": 0, - "isBadThreshold": 50, + "isBadThreshold": 75, "isAbnormalThreshold": 300, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -845,7 +845,7 @@ var policies = { "label": "Universal selectors", "message": "

Universal selectors are the most expensive CSS selectors.

More informations here.

", "isOkThreshold": 0, - "isBadThreshold": 40, + "isBadThreshold": 50, "isAbnormalThreshold": 150, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -863,7 +863,7 @@ var policies = { "label": "Redundant body selectors", "message": "

This is one way to remove complexity from a CSS rule. Generally, when \"body\" is specified in a rule it can be removed, because an element is necessarily inside the body.

", "isOkThreshold": 0, - "isBadThreshold": 50, + "isBadThreshold": 60, "isAbnormalThreshold": 200, "hasOffenders": true, "offendersTransformFn": function(offenders) { @@ -884,7 +884,7 @@ var policies = { "label": "Redundant tags selectors", "message": "

Some tags included inside other tags are obvious. For example, when \"ul li\" is specified in a rule, \"ul\" can be removed because the \"li\" element is always inside a \"ul\". Same thing for \"tr td\", \"select option\", ...

Lowering compexity in CSS selectors can make the page load a little faster.

", "isOkThreshold": 0, - "isBadThreshold": 50, + "isBadThreshold": 60, "isAbnormalThreshold": 200, "hasOffenders": true, "offendersTransformFn": function(offenders) { From eab48ea202427ba90e6f73b4ce28886dc1b90074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 19:51:43 +0200 Subject: [PATCH 19/32] Fix screenshot bug when opeing an v1.3 result in 1.4 --- front/src/views/dashboard.html | 2 +- front/src/views/screenshot.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index cdd8a7f..5b4455e 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -11,7 +11,7 @@
-
+
diff --git a/front/src/views/screenshot.html b/front/src/views/screenshot.html index 31a4b67..e2be19e 100644 --- a/front/src/views/screenshot.html +++ b/front/src/views/screenshot.html @@ -2,7 +2,7 @@

Screenshot

-
+
From 5b5fb3e32de12b524430d34744cecb37ffca527b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 23:07:55 +0200 Subject: [PATCH 20/32] Fix profiler details for jquery - on --- front/src/js/directives/offendersDirectives.js | 4 +++- lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js | 8 +++++++- test/www/jquery-page.html | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js index c22eed8..9ef94b2 100644 --- a/front/src/js/directives/offendersDirectives.js +++ b/front/src/js/directives/offendersDirectives.js @@ -346,7 +346,9 @@ case 'jQuery - on': case 'jQuery - one': - if (args[1]) { + if (isStringOfObject(args[0])) { + return '' + args[0].replace(/"\(function\)"/g, '(function)') + ''; + } else if (args[1] && isPureString(args[1])) { return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine) + '\'s children filtered by ' + args[1] + ''; } else { return 'bind ' + args[0] + ' on ' + getJQueryContextButtonHTML(ctxt, onASingleLine); diff --git a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js index f2403a8..c29b85a 100644 --- a/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js +++ b/lib/tools/phantomas/custom_modules/modules/jQYLT/jQYLT.js @@ -274,7 +274,6 @@ exports.module = function(phantomas) { args = [].slice.call(arguments); args.forEach(function(arg, index) { - if (arg instanceof Array) { arg = '[Array]'; } @@ -296,6 +295,13 @@ exports.module = function(phantomas) { } else { try { + + for (var key in arg) { + if (typeof arg[key] === 'function') { + arg[key] = '(function)'; + } + } + arg = JSON.stringify(arg); } catch(e) { arg = '[Object]'; diff --git a/test/www/jquery-page.html b/test/www/jquery-page.html index 2e29bbe..34f53cc 100644 --- a/test/www/jquery-page.html +++ b/test/www/jquery-page.html @@ -102,6 +102,7 @@ $tbodytr.on('click', handler); $tbody.on('click', 'tr', handler); $tbody.on('click', {someData: 'Joe'}, handler); + $tbody.on({event1: handler, event2: handler}); $tbodytr.off('click', handler); $tbodytr.off('click', 'tr'); $tbodytr.off('click'); From eaeff3bf6a558b1146e68d0861f4063c8029c58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Thu, 7 May 2015 23:16:13 +0200 Subject: [PATCH 21/32] Display help if CLI is launched without an url --- bin/cli.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 7d4d4a0..58a8fb4 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -28,8 +28,7 @@ var cli = meow({ // Check parameters if (cli.input.length < 1) { - console.error('Incorrect parameters: url not provided'); - process.exit(1); + cli.showHelp(); } var url = cli.input[0]; From 6c47c406cb597066eeb99a49e54d2a4104c72f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Sun, 10 May 2015 14:51:36 +0200 Subject: [PATCH 22/32] Prevent fail when there's a screenshot error --- front/src/css/main.css | 3 +++ front/src/less/main.less | 4 ++++ front/src/views/dashboard.html | 3 ++- front/src/views/screenshot.html | 3 ++- lib/server/controllers/apiController.js | 8 +++++++- 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/front/src/css/main.css b/front/src/css/main.css index bc9cb47..fed5912 100644 --- a/front/src/css/main.css +++ b/front/src/css/main.css @@ -198,6 +198,9 @@ a.linkButton { .screenshotWrapper .screenshotImage { width: 100%; } +.screenshotWrapper .screenshotError { + color: #fff; +} .screenshotWrapper.desktop { border: 0.2em solid #AAA; padding: 0.5em; diff --git a/front/src/less/main.less b/front/src/less/main.less index bf52219..ff5d31c 100644 --- a/front/src/less/main.less +++ b/front/src/less/main.less @@ -139,6 +139,10 @@ a.linkButton { .screenshotImage { width: 100%; } + + .screenshotError { + color: #fff; + } } .screenshotWrapper.desktop { diff --git a/front/src/views/dashboard.html b/front/src/views/dashboard.html index 5b4455e..fa97376 100644 --- a/front/src/views/dashboard.html +++ b/front/src/views/dashboard.html @@ -13,7 +13,8 @@
- + + Screenshot not available
diff --git a/front/src/views/screenshot.html b/front/src/views/screenshot.html index e2be19e..7f4de0d 100644 --- a/front/src/views/screenshot.html +++ b/front/src/views/screenshot.html @@ -4,7 +4,8 @@
- + + Screenshot not available
diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index 27e1adf..501c786 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -109,8 +109,14 @@ var ApiController = function(app) { } }) + // Delete screenshot temporary file - .then(screenshot.deleteTmpFile); + .then(screenshot.deleteTmpFile) + + // Don't worry if there's an error + .fail(function(err) { + debug('An error occured while creating the screenshot\'s thumbnail. Ignoring and continuing...'); + }); } From 118ec757a3fedfe7133a53c9fc8a2744e4f991de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 10:53:43 +0200 Subject: [PATCH 23/32] Update dependencies --- bower.json | 2 +- package.json | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bower.json b/bower.json index 15139ff..acfc53c 100644 --- a/bower.json +++ b/bower.json @@ -6,7 +6,7 @@ "angular-resource": "~1.3.15", "angular-sanitize": "~1.3.15", "angular-animate": "~1.3.15", - "angular-local-storage": "~0.1.5" + "angular-local-storage": "~0.2.0" }, "resolutions": { "angular": "~1.3.8" diff --git a/package.json b/package.json index 7b03965..ab6d41a 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,16 @@ "main": "./lib/index.js", "dependencies": { "async": "~0.9.0", - "body-parser": "~1.12.3", + "body-parser": "~1.12.4", "compression": "~1.4.3", "cors": "^2.6.0", - "debug": "~2.1.3", + "debug": "~2.2.0", "express": "~4.12.3", "lwip": "0.0.6", "meow": "^3.1.0", - "phantomas": "1.10.1", + "phantomas": "1.10.2", "ps-node": "0.0.4", - "q": "~1.3.0", + "q": "~1.4.0", "rimraf": "~2.3.3", "temporary": "0.0.8" }, @@ -32,7 +32,7 @@ "grunt-contrib-clean": "^0.6.0", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-copy": "^0.8.0", - "grunt-contrib-cssmin": "^0.12.2", + "grunt-contrib-cssmin": "^0.12.3", "grunt-contrib-htmlmin": "^0.4.0", "grunt-contrib-jshint": "^0.11.2", "grunt-contrib-less": "^1.0.1", @@ -43,9 +43,9 @@ "grunt-inline-angular-templates": "^0.1.5", "grunt-line-remover": "^0.0.2", "grunt-mocha-test": "^0.12.7", - "grunt-replace": "^0.8.0", + "grunt-replace": "^0.9.2", "grunt-usemin": "^3.0.0", - "grunt-webfont": "^0.5.2", + "grunt-webfont": "^0.5.3", "matchdep": "^0.3.0", "mocha": "^2.2.4", "request": "^2.55.0", From aba91de4801ce491833c5ff849a22a05e2355e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 16:59:32 +0200 Subject: [PATCH 24/32] Add an exclude parameter to get result in API --- lib/server/controllers/apiController.js | 13 +++ test/api/apiTest.js | 104 ++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index 501c786..cd211b0 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -249,6 +249,19 @@ var ApiController = function(app) { // Retrive one result by id app.get('/api/results/:id', function(req, res) { getPartialResults(req.params.id, res, function(data) { + + // Some fields can be excluded from the response, this way: + // /api/results/:id?exclude=field1,field2 + if (req.query.exclude && typeof req.query.exclude === 'string') { + var excludedFields = req.query.exclude.split(','); + excludedFields.forEach(function(fieldName) { + if (data[fieldName]) { + console.log('excluding ' + fieldName); + delete data[fieldName]; + } + }); + } + return data; }); }); diff --git a/test/api/apiTest.js b/test/api/apiTest.js index 79886ec..c13701f 100644 --- a/test/api/apiTest.js +++ b/test/api/apiTest.js @@ -544,6 +544,110 @@ describe('api', function() { }); + it('should return the entire object and exclude toolsResults', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.equals(asyncRunId); + body.should.have.a.property('params').that.is.an('object'); + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.should.have.a.property('rules').that.is.an('object'); + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + + body.should.not.have.a.property('toolsResults').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + + it('should return the entire object and exclude params and toolsResults', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '?exclude=toolsResults,params', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.equals(asyncRunId); + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.should.have.a.property('rules').that.is.an('object'); + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + + body.should.not.have.a.property('params').that.is.an('object'); + body.should.not.have.a.property('toolsResults').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + it('should return the entire object and don\'t exclude anything', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '?exclude=', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.equals(asyncRunId); + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.should.have.a.property('rules').that.is.an('object'); + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + body.should.have.a.property('params').that.is.an('object'); + body.should.have.a.property('toolsResults').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + it('should return the entire object and don\'t exclude anything', function(done) { + this.timeout(5000); + + request({ + method: 'GET', + url: serverUrl + '/api/results/' + asyncRunId + '?exclude=null', + json: true, + }, function(error, response, body) { + if (!error && response.statusCode === 200) { + + body.should.have.a.property('runId').that.equals(asyncRunId); + body.should.have.a.property('scoreProfiles').that.is.an('object'); + body.should.have.a.property('rules').that.is.an('object'); + body.should.have.a.property('javascriptExecutionTree').that.is.an('object'); + body.should.have.a.property('params').that.is.an('object'); + body.should.have.a.property('toolsResults').that.is.an('object'); + + done(); + + } else { + done(error || response.statusCode); + } + }); + }); + + it('should retrieve the screenshot', function(done) { this.timeout(5000); From 5f6ba048e635e6fe0953d8bbbeafbd86a31f50ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 17:00:58 +0200 Subject: [PATCH 25/32] Make the results API call smaller by removing toolsResults --- front/src/js/controllers/dashboardCtrl.js | 2 +- front/src/js/controllers/ruleCtrl.js | 2 +- front/src/js/controllers/screenshotCtrl.js | 2 +- front/src/js/controllers/timelineCtrl.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/js/controllers/dashboardCtrl.js b/front/src/js/controllers/dashboardCtrl.js index a07d1e6..0b5732e 100644 --- a/front/src/js/controllers/dashboardCtrl.js +++ b/front/src/js/controllers/dashboardCtrl.js @@ -8,7 +8,7 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams function loadResults() { // Load result if needed if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) { - Results.get({runId: $routeParams.runId}, function(result) { + Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) { $rootScope.loadedResult = result; $scope.result = result; init(); diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js index 12d97bc..1d4c2c2 100644 --- a/front/src/js/controllers/ruleCtrl.js +++ b/front/src/js/controllers/ruleCtrl.js @@ -9,7 +9,7 @@ ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$locat function loadResults() { // Load result if needed if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) { - Results.get({runId: $routeParams.runId}, function(result) { + Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) { $rootScope.loadedResult = result; $scope.result = result; init(); diff --git a/front/src/js/controllers/screenshotCtrl.js b/front/src/js/controllers/screenshotCtrl.js index 0d2b60d..6bd5fbf 100644 --- a/front/src/js/controllers/screenshotCtrl.js +++ b/front/src/js/controllers/screenshotCtrl.js @@ -7,7 +7,7 @@ screenshotCtrl.controller('ScreenshotCtrl', ['$scope', '$rootScope', '$routePara function loadResults() { // Load result if needed if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) { - Results.get({runId: $routeParams.runId}, function(result) { + Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) { $rootScope.loadedResult = result; $scope.result = result; }, function(err) { diff --git a/front/src/js/controllers/timelineCtrl.js b/front/src/js/controllers/timelineCtrl.js index 799da5a..3887ff1 100644 --- a/front/src/js/controllers/timelineCtrl.js +++ b/front/src/js/controllers/timelineCtrl.js @@ -7,7 +7,7 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams', function loadResults() { // Load result if needed if (!$rootScope.loadedResult || $rootScope.loadedResult.runId !== $routeParams.runId) { - Results.get({runId: $routeParams.runId}, function(result) { + Results.get({runId: $routeParams.runId, exclude: 'toolsResults'}, function(result) { $rootScope.loadedResult = result; $scope.result = result; render(); From f9e60acdf63d2e96d3e8bd43f554a720077d15c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 17:16:05 +0200 Subject: [PATCH 26/32] Reduce timeout from 60s to 45 --- lib/tools/phantomas/phantomasWrapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tools/phantomas/phantomasWrapper.js b/lib/tools/phantomas/phantomasWrapper.js index 5b58b53..f4d85a0 100644 --- a/lib/tools/phantomas/phantomasWrapper.js +++ b/lib/tools/phantomas/phantomasWrapper.js @@ -20,7 +20,7 @@ var PhantomasWrapper = function() { var options = { // Cusomizable options - 'timeout': task.options.timeout || 60, + 'timeout': task.options.timeout || 45, 'js-deep-analysis': task.options.jsDeepAnalysis || false, 'user-agent': (task.options.device === 'desktop') ? 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36' : null, 'tablet': (task.options.device === 'tablet'), From cee47f28460dbdf1e511fe3956e51f6b158de228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 18:26:41 +0200 Subject: [PATCH 27/32] Remove temporarily domAccess from dashboard --- lib/metadata/scoreProfileGeneric.json | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index 841444e..e682259 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -12,7 +12,6 @@ "domManipulations": { "label": "DOM manipulations", "policies": { - "DOMaccesses": 3, "DOMinserts": 2, "DOMqueries": 1, "DOMqueriesWithoutResults": 2, From a7051ace5d03056e789de4bffc9ee0632c27ac1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 18:28:11 +0200 Subject: [PATCH 28/32] Add metrics queriesWithoutResults, jQueryCalls, jQueryCallsOnEmptyObject, jQueryNotDelegatedEvent --- .../src/js/directives/offendersDirectives.js | 6 +++-- lib/tools/jsExecutionTransformer.js | 24 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js index 9ef94b2..c147520 100644 --- a/front/src/js/directives/offendersDirectives.js +++ b/front/src/js/directives/offendersDirectives.js @@ -670,8 +670,10 @@ if (node.data.callDetails.context && node.data.callDetails.context.length === 0) { html += '

Called on 0 jQuery element

Useless function call, as the jQuery object is empty.

'; - } else if (node.data.type === 'jQuery - bind' && node.data.callDetails.context.length > 5) { - html += '

The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method is preferable if available (from v1.7).

'; + } else if (node.data.type === 'jQuery - bind' && node.data.callDetails.context.length > 3) { + html += '

The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method with event delegation is preferable if available (from v1.7).

'; + } else if (node.data.type === 'jQuery - on' && node.data.callDetails.context.length > 3) { + html += '

The .on() method used this way attaches the event listener to each jQuery element one by one. Using the event delegation version of the method is preferable if available (from v1.7).

'; } if (node.data.resultsNumber === 0) { diff --git a/lib/tools/jsExecutionTransformer.js b/lib/tools/jsExecutionTransformer.js index 188623e..577d255 100644 --- a/lib/tools/jsExecutionTransformer.js +++ b/lib/tools/jsExecutionTransformer.js @@ -10,10 +10,11 @@ var jsExecutionTransformer = function() { var metrics = { DOMaccesses: 0, + DOMaccessesOnScroll: 0, queriesWithoutResults: 0, jQueryCalls: 0, jQueryCallsOnEmptyObject: 0, - DOMaccessesOnScroll: 0 + jQueryNotDelegatedEvent: 0 }; try { @@ -24,14 +25,27 @@ var jsExecutionTransformer = function() { if (javascriptExecutionTree.children) { javascriptExecutionTree.children.forEach(function(node) { - // Mark abnormal things with a warning flag var contextLenght = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null; - if ((node.data.type === 'jQuery - bind' && contextLenght > 5) || - node.data.resultsNumber === 0 || - contextLenght === 0) { + + if ((node.data.type === 'jQuery - bind' || node.data.type === 'jQuery - on') && contextLenght > 3) { + metrics.jQueryNotDelegatedEvent += contextLenght - 1; node.warning = true; } + if (node.data.resultsNumber === 0) { + metrics.queriesWithoutResults ++; + node.warning = true; + } + + if (contextLenght === 0) { + metrics.jQueryCallsOnEmptyObject ++; + node.warning = true; + } + + if (node.data.type.indexOf('jQuery - ') === 0) { + metrics.jQueryCalls ++; + } + // Mark errors with an error flag if (node.data.type === 'error' || node.data.type === 'jQuery version change') { node.error = true; From 4e6a88abec730e836c822b825848f8807f5de13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Mon, 11 May 2015 19:43:15 +0200 Subject: [PATCH 29/32] New jQuery usage rule --- front/src/views/rule.html | 4 +++ lib/metadata/policies.js | 13 ++++++++-- lib/metadata/scoreProfileGeneric.json | 11 ++++---- lib/tools/jsExecutionTransformer.js | 37 ++++++++++++++++++++++----- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 6ec11ab..5badf0a 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -79,6 +79,10 @@
+
+ Function {{offender.functionName}} used {{offender.count}} times +
+
{{offender.writeFn}} diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index b7eade5..f7877a5 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -66,7 +66,7 @@ var policies = { }; } }, - "DOMaccesses": { + /*"DOMaccesses": { "tool": "jsExecutionTransformer", "label": "DOM access", "message": "

TODO

TODO

", @@ -74,7 +74,7 @@ var policies = { "isBadThreshold": 2000, "isAbnormalThreshold": 3000, "hasOffenders": false - }, + },*/ "DOMinserts": { "tool": "phantomas", "label": "DOM inserts", @@ -430,6 +430,15 @@ var policies = { "isAbnormalThreshold": 2, "hasOffenders": true }, + "jQueryFunctionsUsed": { + "tool": "jsExecutionTransformer", + "label": "jQuery usage", + "message": "

This is the number of different jQuery functions called on load. This rule is not trying to blame you for using jQuery too much, but the opposite.

If only a few functions are used, why not trying to get rid of jQuery? Have a look at http://youmightnotneedjquery.com.

", + "isOkThreshold": 15, + "isBadThreshold": 6, + "isAbnormalThreshold": 0, + "hasOffenders": true + }, "cssParsingErrors": { "tool": "phantomas", "label": "CSS syntax error", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index e682259..df7400e 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -35,11 +35,12 @@ "globalVariables": 0.5 } }, - "jQueryVersion": { - "label": "jQuery version", + "jQuery": { + "label": "jQuery", "policies": { - "jQueryVersion": 5, - "jQueryVersionsLoaded": 0.1 + "jQueryVersion": 10, + "jQueryVersionsLoaded": 1, + "jQueryFunctionsUsed": 1 } }, "cssComplexity": { @@ -109,7 +110,7 @@ "domManipulations": 2, "scroll": 1, "badJavascript": 1, - "jQueryVersion": 1, + "jQuery": 1, "cssSyntaxError": 1, "cssComplexity": 1, "badCSS": 1, diff --git a/lib/tools/jsExecutionTransformer.js b/lib/tools/jsExecutionTransformer.js index 577d255..b15ccd1 100644 --- a/lib/tools/jsExecutionTransformer.js +++ b/lib/tools/jsExecutionTransformer.js @@ -1,12 +1,14 @@ var debug = require('debug')('ylt:jsExecutionTransformer'); -var offendersHelpers = require('../offendersHelpers'); +var offendersHelpers = require('../offendersHelpers'); +var Collection = require('./phantomas/custom_modules/util/collection'); var jsExecutionTransformer = function() { this.transform = function(data) { var javascriptExecutionTree = {}; var scrollExecutionTree = {}; + var jQueryFunctionsCollection = new Collection(); var metrics = { DOMaccesses: 0, @@ -17,6 +19,10 @@ var jsExecutionTransformer = function() { jQueryNotDelegatedEvent: 0 }; + var offenders = { + + }; + try { debug('Starting JS execution transformation'); @@ -44,6 +50,7 @@ var jsExecutionTransformer = function() { if (node.data.type.indexOf('jQuery - ') === 0) { metrics.jQueryCalls ++; + jQueryFunctionsCollection.push(node.data.type); } // Mark errors with an error flag @@ -78,14 +85,32 @@ var jsExecutionTransformer = function() { // Count the number of DOM accesses, by counting the tree leafs metrics.DOMaccesses += countTreeLeafs(node); }); + + // Count the number of different jQuery functions called + if (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded) { + metrics.jQueryFunctionsUsed = 0; + offenders.jQueryFunctionsUsed = []; + + jQueryFunctionsCollection.sort().forEach(function(fnName, cnt) { + if (fnName === 'jQuery - find') { + fnName = 'jQuery - $'; + } + metrics.jQueryFunctionsUsed ++; + offenders.jQueryFunctionsUsed.push({ + functionName: fnName.substring(9), + count: cnt + }); + }); + } + } debug('JS execution transformation complete'); debug('Starting scroll execution transformation'); - scrollExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.scrollExecutionTree[0]); - if (scrollExecutionTree.children) { - scrollExecutionTree.children.forEach(function(node) { + offenders.scrollExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.scrollExecutionTree[0]); + if (offenders.scrollExecutionTree.children) { + offenders.scrollExecutionTree.children.forEach(function(node) { // Mark a event flag if (['documentScroll', 'windowScroll', 'window.onscroll'].indexOf(node.data.type) >= 0) { @@ -109,9 +134,7 @@ var jsExecutionTransformer = function() { data.toolsResults.jsExecutionTransformer = { metrics: metrics, - offenders: { - DOMaccessesOnScroll: scrollExecutionTree - } + offenders: offenders }; return data; From d7d4fb36ad444ed2dc1f47be8227f05287ff3026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 12 May 2015 07:47:16 +0200 Subject: [PATCH 30/32] Remove console log --- lib/server/controllers/apiController.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/server/controllers/apiController.js b/lib/server/controllers/apiController.js index cd211b0..d318e22 100644 --- a/lib/server/controllers/apiController.js +++ b/lib/server/controllers/apiController.js @@ -256,7 +256,6 @@ var ApiController = function(app) { var excludedFields = req.query.exclude.split(','); excludedFields.forEach(function(fieldName) { if (data[fieldName]) { - console.log('excluding ' + fieldName); delete data[fieldName]; } }); From 9bc6d2928b926e7fa2cd67f21217c8fd1e887f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 12 May 2015 09:12:10 +0200 Subject: [PATCH 31/32] New Events Not Delegated rule --- .../src/js/directives/offendersDirectives.js | 8 +-- front/src/views/rule.html | 15 +++++ lib/metadata/policies.js | 9 +++ lib/metadata/scoreProfileGeneric.json | 5 +- lib/tools/jsExecutionTransformer.js | 58 +++++++++++++++---- test/www/jquery-page.html | 1 + 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js index c147520..df2b35c 100644 --- a/front/src/js/directives/offendersDirectives.js +++ b/front/src/js/directives/offendersDirectives.js @@ -109,7 +109,7 @@ } else if (context.length > 3) { html += ' and ' + (context.length - 2) + ' more...'; } - return html + '}'; + return html + ')'; } function isJQuery(node) { @@ -670,10 +670,8 @@ if (node.data.callDetails.context && node.data.callDetails.context.length === 0) { html += '

Called on 0 jQuery element

Useless function call, as the jQuery object is empty.

'; - } else if (node.data.type === 'jQuery - bind' && node.data.callDetails.context.length > 3) { - html += '

The .bind() method attaches the event listener to each jQuery element one by one. Using the .on() method with event delegation is preferable if available (from v1.7).

'; - } else if (node.data.type === 'jQuery - on' && node.data.callDetails.context.length > 3) { - html += '

The .on() method used this way attaches the event listener to each jQuery element one by one. Using the event delegation version of the method is preferable if available (from v1.7).

'; + } else if (node.eventNotDelegated) { + html += '

This binding should use Event Delegation instead of binding each element one by one.

'; } if (node.data.resultsNumber === 0) { diff --git a/front/src/views/rule.html b/front/src/views/rule.html index 5badf0a..b54833a 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -83,6 +83,21 @@ Function {{offender.functionName}} used {{offender.count}} times
+
+ function {{offender.functionName}} used on {{offender.contextLength}} DOM elements without event delegation +
no backtrace
+
+ backtrace +
+
+ {{obj.functionName}}() + + line {{obj.line}} +
+
+
+
+
{{offender.writeFn}} diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index f7877a5..a203b1c 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -439,6 +439,15 @@ var policies = { "isAbnormalThreshold": 0, "hasOffenders": true }, + "jQueryNotDelegatedEvents": { + "tool": "jsExecutionTransformer", + "label": "Events not delegated", + "message": "

This is the number of events that are bound with the .bind() or the .on() function without using event delegation.

This means jQuery binds each element contained in the object one by one. This is bad for performance.

", + "isOkThreshold": 1, + "isBadThreshold": 100, + "isAbnormalThreshold": 180, + "hasOffenders": true + }, "cssParsingErrors": { "tool": "phantomas", "label": "CSS syntax error", diff --git a/lib/metadata/scoreProfileGeneric.json b/lib/metadata/scoreProfileGeneric.json index df7400e..aa2590c 100644 --- a/lib/metadata/scoreProfileGeneric.json +++ b/lib/metadata/scoreProfileGeneric.json @@ -38,9 +38,10 @@ "jQuery": { "label": "jQuery", "policies": { - "jQueryVersion": 10, + "jQueryVersion": 1, "jQueryVersionsLoaded": 1, - "jQueryFunctionsUsed": 1 + "jQueryFunctionsUsed": 1, + "jQueryNotDelegatedEvents": 1 } }, "cssComplexity": { diff --git a/lib/tools/jsExecutionTransformer.js b/lib/tools/jsExecutionTransformer.js index b15ccd1..6f5a07f 100644 --- a/lib/tools/jsExecutionTransformer.js +++ b/lib/tools/jsExecutionTransformer.js @@ -13,16 +13,24 @@ var jsExecutionTransformer = function() { var metrics = { DOMaccesses: 0, DOMaccessesOnScroll: 0, - queriesWithoutResults: 0, - jQueryCalls: 0, - jQueryCallsOnEmptyObject: 0, - jQueryNotDelegatedEvent: 0 + queriesWithoutResults: 0 }; var offenders = { }; + var hasjQuery = (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded > 0); + if (hasjQuery) { + metrics.jQueryCalls = 0; + metrics.jQueryFunctionsUsed = 0; + metrics.jQueryCallsOnEmptyObject = 0; + metrics.jQueryNotDelegatedEvents = 0; + + offenders.jQueryFunctionsUsed = []; + offenders.jQueryNotDelegatedEvents = []; + } + try { debug('Starting JS execution transformation'); @@ -31,11 +39,17 @@ var jsExecutionTransformer = function() { if (javascriptExecutionTree.children) { javascriptExecutionTree.children.forEach(function(node) { - var contextLenght = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null; + var contextLength = (node.data.callDetails && node.data.callDetails.context) ? node.data.callDetails.context.length : null; - if ((node.data.type === 'jQuery - bind' || node.data.type === 'jQuery - on') && contextLenght > 3) { - metrics.jQueryNotDelegatedEvent += contextLenght - 1; + if (isABindWithoutEventDelegation(node, contextLength)) { + metrics.jQueryNotDelegatedEvents += contextLength; + offenders.jQueryNotDelegatedEvents.push({ + functionName: node.data.type.substring(9), + contextLength: contextLength, + backtrace: offendersHelpers.backtraceToArray(node.data.backtrace) + }); node.warning = true; + node.eventNotDelegated = true; } if (node.data.resultsNumber === 0) { @@ -43,7 +57,7 @@ var jsExecutionTransformer = function() { node.warning = true; } - if (contextLenght === 0) { + if (contextLength === 0) { metrics.jQueryCallsOnEmptyObject ++; node.warning = true; } @@ -87,9 +101,7 @@ var jsExecutionTransformer = function() { }); // Count the number of different jQuery functions called - if (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded) { - metrics.jQueryFunctionsUsed = 0; - offenders.jQueryFunctionsUsed = []; + if (hasjQuery) { jQueryFunctionsCollection.sort().forEach(function(fnName, cnt) { if (fnName === 'jQuery - find') { @@ -181,6 +193,30 @@ var jsExecutionTransformer = function() { return count; } + + function isPureString(str) { + return typeof str === 'string' && str[0] !== '{' && str !== '(function)' && str !== '[Object]' && str !== '[Array]' && str !== 'true' && str !== 'false' && str !== 'undefined' && str !== 'unknown' && str !== 'null'; + } + + function isABindWithoutEventDelegation(node, contextLength) { + // Count only on larger bindings + if (contextLength <= 3) { + return false; + } + + if (node.data.type === 'jQuery - on' && node.data.callDetails.arguments[1] && !isPureString(node.data.callDetails.arguments[1])) { + return true; + } + + if (node.data.type.indexOf('jQuery - ') === 0 && node.children && node.children.length === 1) { + var child = node.children[0]; + if (child.data.type === 'jQuery - on' && child.data.callDetails.arguments[1] && !isPureString(child.data.callDetails.arguments[1])) { + return true; + } + } + + return false; + } }; module.exports = new jsExecutionTransformer(); \ No newline at end of file diff --git a/test/www/jquery-page.html b/test/www/jquery-page.html index 34f53cc..f2e3f16 100644 --- a/test/www/jquery-page.html +++ b/test/www/jquery-page.html @@ -20,6 +20,7 @@ // Let's start the spaghetti code!!! + $('li').bind('mouseover', function() {}); $('ul').find('li'); $('li', $('#foo')); var foo = document.getElementById('foo'); From 4ccd2d7087a72d911b946096b48fe9ce3e62b7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Tue, 12 May 2015 09:32:46 +0200 Subject: [PATCH 32/32] v1.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab6d41a..13ed17f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yellowlabtools", - "version": "1.4.1", + "version": "1.5.0", "description": "Online tool to audit a webpage for performance and front-end quality issues", "repository": { "type": "git",