Pārlūkot izejas kodu

Merge pull request #81 from gmetais/develop

v1.6.0
Gaël Métais 10 gadi atpakaļ
vecāks
revīzija
e67a0e0bc0
34 mainītis faili ar 288 papildinājumiem un 391 dzēšanām
  1. 2 0
      bin/server.js
  2. 0 0
      front/src/css/icons.css
  3. 0 0
      front/src/css/main.css
  4. 24 2
      front/src/css/timeline.css
  5. 0 0
      front/src/fonts/svg-icons/arrow-left3.svg
  6. 0 0
      front/src/fonts/svg-icons/bars.svg
  7. 0 0
      front/src/fonts/svg-icons/lab.svg
  8. 0 0
      front/src/fonts/svg-icons/list.svg
  9. 0 0
      front/src/fonts/svg-icons/loop.svg
  10. 0 0
      front/src/fonts/svg-icons/mobile.svg
  11. 0 0
      front/src/fonts/svg-icons/question.svg
  12. 0 0
      front/src/fonts/svg-icons/screen.svg
  13. 0 0
      front/src/fonts/svg-icons/tablet.svg
  14. 0 0
      front/src/fonts/svg-icons/warning.svg
  15. 12 0
      front/src/js/app.js
  16. 4 5
      front/src/js/controllers/dashboardCtrl.js
  17. 18 0
      front/src/js/controllers/timelineCtrl.js
  18. 14 1
      front/src/js/directives/offendersDirectives.js
  19. 0 0
      front/src/less/icons.less
  20. 31 2
      front/src/less/timeline.less
  21. 2 2
      front/src/views/index.html
  22. 20 28
      front/src/views/rule.html
  23. 30 11
      front/src/views/timeline.html
  24. 29 108
      lib/metadata/policies.js
  25. 6 7
      lib/metadata/scoreProfileGeneric.json
  26. 6 0
      lib/server/datastores/runsDatastore.js
  27. 12 0
      lib/server/middlewares/wwwRedirectMiddleware.js
  28. 23 24
      lib/tools/jsExecutionTransformer.js
  29. 6 1
      lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js
  30. 28 9
      lib/tools/phantomas/phantomasWrapper.js
  31. 7 7
      package.json
  32. 1 181
      test/core/customPoliciesTest.js
  33. 9 3
      test/core/indexTest.js
  34. 4 0
      test/www/simple-page.html

+ 2 - 0
bin/server.js

@@ -7,12 +7,14 @@ var cors                    = require('cors');
 
 
 var authMiddleware          = require('../lib/server/middlewares/authMiddleware');
 var authMiddleware          = require('../lib/server/middlewares/authMiddleware');
 var apiLimitsMiddleware     = require('../lib/server/middlewares/apiLimitsMiddleware');
 var apiLimitsMiddleware     = require('../lib/server/middlewares/apiLimitsMiddleware');
+var wwwRedirectMiddleware   = require('../lib/server/middlewares/wwwRedirectMiddleware');
 
 
 
 
 // Middlewares
 // Middlewares
 app.use(compress());
 app.use(compress());
 app.use(bodyParser.json());
 app.use(bodyParser.json());
 app.use(cors());
 app.use(cors());
+app.use(wwwRedirectMiddleware);
 app.use(authMiddleware);
 app.use(authMiddleware);
 app.use(apiLimitsMiddleware);
 app.use(apiLimitsMiddleware);
 
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/css/icons.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/css/main.css


+ 24 - 2
front/src/css/timeline.css

@@ -143,6 +143,10 @@
   border: 1px dotted #aaa;
   border: 1px dotted #aaa;
   text-align: left;
   text-align: left;
 }
 }
+.subFilters {
+  margin-left: 3em;
+  font-size: 0.9em;
+}
 .table {
 .table {
   display: table;
   display: table;
   width: 100%;
   width: 100%;
@@ -306,9 +310,27 @@
   color: #e74c3c;
   color: #e74c3c;
   cursor: pointer;
   cursor: pointer;
 }
 }
-.warningsFilterOn > div {
+.queryWithoutResultsFilterOn > div {
+  display: none;
+}
+.queryWithoutResultsFilterOn > div.queryWithoutResults {
+  display: table-row;
+}
+.jQueryCallOnEmptyObjectFilterOn > div {
+  display: none;
+}
+.jQueryCallOnEmptyObjectFilterOn > div.jQueryCallOnEmptyObject {
+  display: table-row;
+}
+.eventNotDelegatedFilterOn > div {
+  display: none;
+}
+.eventNotDelegatedFilterOn > div.eventNotDelegated {
+  display: table-row;
+}
+.jsErrorFilterOn > div {
   display: none;
   display: none;
 }
 }
-.warningsFilterOn > div.warning {
+.jsErrorFilterOn > div.jsError {
   display: table-row;
   display: table-row;
 }
 }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/arrow-left3.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/bars.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/lab.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/list.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/loop.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/mobile.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/question.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/screen.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/tablet.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/fonts/svg-icons/warning.svg


+ 12 - 0
front/src/js/app.js

@@ -19,8 +19,20 @@ var yltApp = angular.module('YellowLabTools', [
 ]);
 ]);
 
 
 yltApp.run(['$rootScope', '$location', function($rootScope, $location) {
 yltApp.run(['$rootScope', '$location', function($rootScope, $location) {
+    $rootScope.isTouchDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent);
     $rootScope.loadedRunId = null;
     $rootScope.loadedRunId = null;
 
 
+    var oldHash;
+
+    // We don't want the hash to be kept between two pages
+    $rootScope.$on('$locationChangeStart', function(param1, param2, param3, param4){
+        var newHash = $location.hash();
+        if (newHash === oldHash) {
+            $location.hash(null);
+        }
+        oldHash = newHash;
+    });
+
     // Google Analytics
     // Google Analytics
     $rootScope.$on('$routeChangeSuccess', function(){
     $rootScope.$on('$routeChangeSuccess', function(){
         ga('send', 'pageview', {'page': $location.path()});
         ga('send', 'pageview', {'page': $location.path()});

+ 4 - 5
front/src/js/controllers/dashboardCtrl.js

@@ -25,8 +25,7 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams
         // By default, Angular sorts object's attributes alphabetically. Countering this problem by retrieving the keys order here.
         // By default, Angular sorts object's attributes alphabetically. Countering this problem by retrieving the keys order here.
         $scope.categoriesOrder = Object.keys($scope.result.scoreProfiles.generic.categories);
         $scope.categoriesOrder = Object.keys($scope.result.scoreProfiles.generic.categories);
         
         
-        $scope.globalScore = Math.max($scope.result.scoreProfiles.generic.globalScore, 0);
-        $scope.tweetText = 'My website\'s score is ' + $scope.globalScore + '/100 on #YellowLabTools!';
+        $scope.tweetText = 'I\'ve discovered this cool open-source tool that audits the front-end quality of a web page: ';
     }
     }
 
 
     $scope.showRulePage = function(ruleName) {
     $scope.showRulePage = function(ruleName) {
@@ -37,18 +36,18 @@ dashboardCtrl.controller('DashboardCtrl', ['$scope', '$rootScope', '$routeParams
         API.relaunchTest($scope.result);
         API.relaunchTest($scope.result);
     };
     };
 
 
-    /// When comming from a social shared link, the user needs to click on "See full report" button to display the full dashboard.
+    // When comming from a social shared link, the user needs to click on "See full report" button to display the full dashboard.
     $scope.seeFullReport = function() {
     $scope.seeFullReport = function() {
         $scope.fromSocialShare = false;
         $scope.fromSocialShare = false;
         $location.search({});
         $location.search({});
     };
     };
 
 
     $scope.shareOnTwitter = function(message) {
     $scope.shareOnTwitter = function(message) {
-        openSocialPopup('https://twitter.com/intent/tweet?url=' + document.URL + '%3Fshare&text=' + encodeURIComponent(message));
+        openSocialPopup('https://twitter.com/intent/tweet?text=' + encodeURIComponent(message + 'http://yellowlab.tools'));
     };
     };
 
 
     $scope.shareOnLinkedin = function(message) {
     $scope.shareOnLinkedin = function(message) {
-        openSocialPopup('https://www.linkedin.com/shareArticle?mini=true&url=' + document.URL + '%3Fshare&title=' + encodeURIComponent(message) + '&summary=' + encodeURIComponent('YellowLabTools is a free online tool that analyzes performance and front-end quality of a webpage.'));
+        openSocialPopup('https://www.linkedin.com/shareArticle?mini=true&url=http://yellowlab.tools&title=' + encodeURIComponent(message) + '&summary=' + encodeURIComponent('YellowLabTools is a free online tool that analyzes performance and front-end quality of a webpage.'));
     };
     };
 
 
     function openSocialPopup(url) {
     function openSocialPopup(url) {

+ 18 - 0
front/src/js/controllers/timelineCtrl.js

@@ -19,12 +19,30 @@ timelineCtrl.controller('TimelineCtrl', ['$scope', '$rootScope', '$routeParams',
     }
     }
 
 
     function render() {
     function render() {
+        initFilters();
         initScriptFiltering();
         initScriptFiltering();
         initExecutionTree();
         initExecutionTree();
         initTimeline();
         initTimeline();
         $timeout(initProfiler, 100);
         $timeout(initProfiler, 100);
     }
     }
 
 
+    function initFilters() {
+        var hash = $location.hash();
+        var filter = null;
+        
+        if (hash.indexOf('filter=') === 0) {
+            filter = hash.substr(7);
+        }
+
+        $scope.warningsFilterOn = (filter !== null);
+        $scope.warningsFilters = {
+            queryWithoutResults: (filter === null || filter === 'queryWithoutResults'),
+            jQueryCallOnEmptyObject: (filter === null || filter === 'jQueryCallOnEmptyObject'),
+            eventNotDelegated: (filter === null || filter === 'eventNotDelegated'),
+            jsError: (filter === null || filter === 'jsError')
+        };
+    }
+
     function initScriptFiltering() {
     function initScriptFiltering() {
         var offenders = $scope.result.rules.jsCount.offendersObj.list;
         var offenders = $scope.result.rules.jsCount.offendersObj.list;
         $scope.scripts = [];
         $scope.scripts = [];

+ 14 - 1
front/src/js/directives/offendersDirectives.js

@@ -766,10 +766,23 @@
                 element.append(getProfilerLineHTML(scope.index, scope.node));
                 element.append(getProfilerLineHTML(scope.index, scope.node));
                 element[0].id = 'line_' + scope.index;
                 element[0].id = 'line_' + scope.index;
 
 
-                if (scope.node.warning || scope.node.error) {
+                if (scope.node.warning) {
                     element[0].classList.add('warning');
                     element[0].classList.add('warning');
+
+                    if (scope.node.queryWithoutResults) {
+                        element[0].classList.add('queryWithoutResults');
+                    }
+
+                    if (scope.node.jQueryCallOnEmptyObject) {
+                        element[0].classList.add('jQueryCallOnEmptyObject');
+                    }
+
+                    if (scope.node.eventNotDelegated) {
+                        element[0].classList.add('eventNotDelegated');
+                    }
                 }
                 }
 
 
+
                 // Bind click on the details icon
                 // Bind click on the details icon
                 var detailsIcon = element[0].querySelector('.details div');
                 var detailsIcon = element[0].querySelector('.details div');
                 if (detailsIcon) {
                 if (detailsIcon) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
front/src/less/icons.less


+ 31 - 2
front/src/less/timeline.less

@@ -162,6 +162,11 @@
     text-align: left;
     text-align: left;
 }
 }
 
 
+.subFilters {
+    margin-left: 3em;
+    font-size: 0.9em;
+}
+
 .table {
 .table {
     display: table;
     display: table;
     width: 100%;
     width: 100%;
@@ -346,10 +351,34 @@
     color: #e74c3c;
     color: #e74c3c;
     cursor: pointer;
     cursor: pointer;
 }
 }
-.warningsFilterOn {
+.queryWithoutResultsFilterOn {
+    > div {
+        display: none;
+        &.queryWithoutResults {
+            display: table-row;
+        }
+    }
+}
+.jQueryCallOnEmptyObjectFilterOn {
+    > div {
+        display: none;
+        &.jQueryCallOnEmptyObject {
+            display: table-row;
+        }
+    }
+}
+.eventNotDelegatedFilterOn {
+    > div {
+        display: none;
+        &.eventNotDelegated {
+            display: table-row;
+        }
+    }
+}
+.jsErrorFilterOn {
     > div {
     > div {
         display: none;
         display: none;
-        &.warning {
+        &.jsError {
             display: table-row;
             display: table-row;
         }
         }
     }
     }

+ 2 - 2
front/src/views/index.html

@@ -2,7 +2,7 @@
 <p class="price">Free and open source!</p>
 <p class="price">Free and open source!</p>
 
 
 <form ng-submit="launchTest()" >
 <form ng-submit="launchTest()" >
-    <input type="text" name="url" ng-model="url" placeholder="http://www.mysite.com" class="url" />
+    <input type="{{$root.isTouchDevice? 'url' : 'text'}}" name="url" ng-model="url" placeholder="http://www.mysite.com" class="url" />
     <input type="submit" value="Launch test" class="launchBtn" ng-class="{disabled: !url}" />
     <input type="submit" value="Launch test" class="launchBtn" ng-class="{disabled: !url}" />
     <div class="settings">
     <div class="settings">
         <div class="device">
         <div class="device">
@@ -90,4 +90,4 @@
         <h3>JS Profiling</h3>
         <h3>JS Profiling</h3>
         <p>Untangles the JavaScript spaghetti code</p>
         <p>Untangles the JavaScript spaghetti code</p>
     </div>
     </div>
-</div>
+</div>

+ 20 - 28
front/src/views/rule.html

@@ -31,22 +31,10 @@
                         <b>{{offender.id}}</b>: {{offender.occurrences}} occurrences
                         <b>{{offender.id}}</b>: {{offender.occurrences}} occurrences
                     </div>
                     </div>
 
 
-                    <div ng-if="policyName === 'DOMinserts'">
-                        <dom-element-button obj="offender.insertedElement"></dom-element-button> appended to <dom-element-button obj="offender.receiverElement"></dom-element-button>
-                    </div>
-                    
-                    <div ng-if="policyName === 'DOMqueriesWithoutResults'">
-                        <b>{{offender.query}}</b> (in <dom-element-button obj="offender.context"></dom-element-button>) using {{offender.fn}}
-                    </div>
-
                     <div ng-if="policyName === 'DOMqueriesAvoidable'">
                     <div ng-if="policyName === 'DOMqueriesAvoidable'">
                         <b>{{offender.query}}</b> (in <dom-element-button obj="offender.context"></dom-element-button>) using {{offender.fn}}: <b>{{offender.count}} queries</b>
                         <b>{{offender.query}}</b> (in <dom-element-button obj="offender.context"></dom-element-button>) using {{offender.fn}}: <b>{{offender.count}} queries</b>
                     </div>
                     </div>
 
 
-                    <div ng-if="policyName === 'eventsBound'">
-                        <b>{{offender.eventName}}</b> bound to <dom-element-button obj="offender.element"></dom-element-button>
-                    </div>
-
                     <div ng-if="policyName === 'eventsScrollBound'">
                     <div ng-if="policyName === 'eventsScrollBound'">
                         <span ng-if="offender.target == 'window'">Scroll event bound on <b>window</b></span>
                         <span ng-if="offender.target == 'window'">Scroll event bound on <b>window</b></span>
                         <span ng-if="offender.target == '#document'">Scroll event bound on <b>document</b></span>
                         <span ng-if="offender.target == '#document'">Scroll event bound on <b>document</b></span>
@@ -83,21 +71,6 @@
                         function <b>{{offender.functionName}}</b> used {{offender.count}} times
                         function <b>{{offender.functionName}}</b> used {{offender.count}} times
                     </div>
                     </div>
 
 
-                    <div ng-if="policyName === 'jQueryNotDelegatedEvents'">
-                        function <b>{{offender.functionName}}</b> used on {{offender.contextLength}} DOM elements without event delegation
-                        <div class="offenderButton" ng-if="offender.backtrace.length == 0">no backtrace</div>
-                        <div class="offenderButton opens" ng-if="offender.backtrace.length > 0">
-                            backtrace
-                            <div class="backtrace">
-                                <div ng-repeat="obj in offender.backtrace track by $index">
-                                    <span ng-if="obj.functionName">{{obj.functionName}}()</span>
-                                    <url-link url="obj.file" max-length="60"></url-link>
-                                    line {{obj.line}}
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-
                     <div ng-if="policyName === 'documentWriteCalls'">
                     <div ng-if="policyName === 'documentWriteCalls'">
                         <b>{{offender.writeFn}}</b>
                         <b>{{offender.writeFn}}</b>
                         <span ng-if="offender.from">
                         <span ng-if="offender.from">
@@ -218,10 +191,29 @@
                     <div ng-repeat="offender in rule.offendersObj.palette" style="background-color: {{offender.color}}; width: {{offender.occurrences * 100 / rule.offendersObj.palette[0].occurrences}}%"><div>{{offender.color}} ({{offender.occurrences}} times)</div></div>
                     <div ng-repeat="offender in rule.offendersObj.palette" style="background-color: {{offender.color}}; width: {{offender.occurrences * 100 / rule.offendersObj.palette[0].occurrences}}%"><div>{{offender.color}} ({{offender.occurrences}} times)</div></div>
                 </div>
                 </div>
             </div>
             </div>
-
         </div>
         </div>
+    </div>
 
 
+    <div ng-if="policyName === 'DOMaccesses'">
+        <h3>{{rule.value}} offenders</h3>
+        Please open the <a href="/result/{{runId}}/timeline">JS timeline</a>
     </div>
     </div>
+
+    <div ng-if="policyName === 'queriesWithoutResults'">
+        <h3>{{rule.value}} offenders</h3>
+        Please open the <a href="/result/{{runId}}/timeline#filter=queryWithoutResults">JS timeline, filtered by "Queries without results"</a>
+    </div>
+
+    <div ng-if="policyName === 'jQueryCallsOnEmptyObject'">
+        <h3>{{rule.value}} offenders</h3>
+        Please open the <a href="/result/{{runId}}/timeline#filter=jQueryCallOnEmptyObject">JS timeline, filtered by "jQuery calls on empty object"</a>
+    </div>
+
+    <div ng-if="policyName === 'jQueryNotDelegatedEvents'">
+        <h3>{{rule.value}} offenders</h3>
+        Please open the <a href="/result/{{runId}}/timeline#filter=eventNotDelegated">JS timeline, filtered by "Events not delegated"</a>
+    </div>
+
     <div ng-if="!rule && rule !== null" class="notFound">
     <div ng-if="!rule && rule !== null" class="notFound">
         <h2>404</h2>
         <h2>404</h2>
         Rule "{{policyName}}"" not found
         Rule "{{policyName}}"" not found

+ 30 - 11
front/src/views/timeline.html

@@ -16,14 +16,14 @@
                 <div ng-repeat="duration in timeline track by $index"
                 <div ng-repeat="duration in timeline track by $index"
                      class="interval"
                      class="interval"
                      ng-class="{
                      ng-class="{
-                        domCreation: $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domInteractive,
-                        domInteractive: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domInteractive
-                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoaded,
-                        domContentLoaded: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoaded
-                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domContentLoadedEnd,
-                        domContentLoadedEnd: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domContentLoadedEnd
-                            && $index * timelineIntervalDuration < result.toolsResults.phantomas.metrics.domComplete,
-                        domComplete: $index * timelineIntervalDuration >= result.toolsResults.phantomas.metrics.domComplete
+                        domCreation: $index * timelineIntervalDuration < result.javascriptExecutionTree.data.domInteractive,
+                        domInteractive: $index * timelineIntervalDuration >= result.javascriptExecutionTree.data.domInteractive
+                            && $index * timelineIntervalDuration < result.javascriptExecutionTree.data.domContentLoaded,
+                        domContentLoaded: $index * timelineIntervalDuration >= result.javascriptExecutionTree.data.domContentLoaded
+                            && $index * timelineIntervalDuration < result.javascriptExecutionTree.data.domContentLoadedEnd,
+                        domContentLoadedEnd: $index * timelineIntervalDuration >= result.javascriptExecutionTree.data.domContentLoadedEnd
+                            && $index * timelineIntervalDuration < result.javascriptExecutionTree.data.domComplete,
+                        domComplete: $index * timelineIntervalDuration >= result.javascriptExecutionTree.data.domComplete
                      }">
                      }">
                     <div style="height: {{100 * duration / timelineMax | number: 0}}px" class="color" ng-class="{clickable: duration > 0}" scroll-on-click="{{$index * timelineIntervalDuration}}"></div>
                     <div style="height: {{100 * duration / timelineMax | number: 0}}px" class="color" ng-class="{clickable: duration > 0}" scroll-on-click="{{$index * timelineIntervalDuration}}"></div>
                     <div class="tooltip detailsOverlay">
                     <div class="tooltip detailsOverlay">
@@ -60,11 +60,30 @@
     </p>
     </p>
     <div class="filters">
     <div class="filters">
         <div>
         <div>
-            <input type="checkbox" ng-model="warningsFilterOn" id="warningsFilterOn" />
-            <label for="warningsFilterOn">Show warnings only</label>
+            <input type="checkbox" name="warningsFilter" ng-model="warningsFilterOn" id="warningsFilterOn" />
+            <label for="warningsFilterOn">Filter on warnings and errors</label>
+            <div class="subFilters" ng-if="warningsFilterOn">
+                <div>
+                    <input type="checkbox" name="filters" ng-model="warningsFilters.queryWithoutResults" id="queryWithoutResultsFilterOn"/>
+                    <label for="queryWithoutResultsFilterOn">Queries without results</label>
+                </div>
+                <div>
+                    <input type="checkbox" name="filters" ng-model="warningsFilters.jQueryCallOnEmptyObject" id="jQueryCallOnEmptyObjectFilterOn" />
+                    <label for="jQueryCallOnEmptyObjectFilterOn">jQuery calls on empty object</label>
+                </div>
+                <div>
+                    <input type="checkbox" name="filters" ng-model="warningsFilters.eventNotDelegated" id="eventNotDelegatedFilterOn" />
+                    <label for="eventNotDelegatedFilterOn">Events not delegated</label>
+                </div>
+                <div>
+                    <input type="checkbox" name="filters" ng-model="warningsFilters.jsError" id="jsErrorFilterOn" />
+                    <label for="jsErrorFilterOn">Errors</label>
+                </div>
+            </div>
         </div>
         </div>
+        {{queryWithoutResultsFilterOn}}
     </div>
     </div>
-    <div class="table" ng-class="{warningsFilterOn: warningsFilterOn}">
+    <div class="table" ng-class="{queryWithoutResultsFilterOn: warningsFilterOn && warningsFilters.queryWithoutResults, jQueryCallOnEmptyObjectFilterOn: warningsFilterOn && warningsFilters.jQueryCallOnEmptyObject, eventNotDelegatedFilterOn: warningsFilterOn && warningsFilters.eventNotDelegated, jsErrorFilterOn: warningsFilterOn && warningsFilters.jsError}">
         <div class="headers">
         <div class="headers">
             <div><!-- index --></div>
             <div><!-- index --></div>
             <div>Type</div>
             <div>Type</div>

+ 29 - 108
lib/metadata/policies.js

@@ -66,82 +66,23 @@ var policies = {
             };
             };
         }
         }
     },
     },
-    /*"DOMaccesses": {
+    "DOMaccesses": {
         "tool": "jsExecutionTransformer",
         "tool": "jsExecutionTransformer",
         "label": "DOM access",
         "label": "DOM access",
-        "message": "<p>TODO</p><p>TODO</p>",
+        "message": "<p>This metric counts the number of calls to DOM related functions (both native DOM functions and jQuery functions) on page load.</p><p>The more your JavaScript code accesses the DOM, the slower the page will load.</p><p>Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.</p><p>Try to reduce the number of queries by refactoring your JavaScript code.</p><p>Binding too many events also has a cost. Try to use <a href=\"https://learn.jquery.com/events/event-delegation/\" target=\"_blank\">event delegation</a> as much as possible.</p>",
         "isOkThreshold": 50,
         "isOkThreshold": 50,
-        "isBadThreshold": 2000,
+        "isBadThreshold": 1500,
         "isAbnormalThreshold": 3000,
         "isAbnormalThreshold": 3000,
         "hasOffenders": false
         "hasOffenders": false
-    },*/
-    "DOMinserts": {
-        "tool": "phantomas",
-        "label": "DOM inserts",
-        "message": "<p>Working with the DOM in JavaScript triggers layout calculations and slows down the page.</p><p>Try, as much as possible, to have an HTML page fully generated by the server instead of making changes with JS.</p>",
-        "isOkThreshold": 10,
-        "isBadThreshold": 400,
-        "isAbnormalThreshold": 1000,
-        "hasOffenders": true,
-        "offendersTransformFn": function(offenders) {
-            return {
-                count: offenders.length,
-                list: offenders.map(function(offender) {
-                    var parts = /^"(.*)" ?appended ?to ?"(.*)"$/.exec(offender);
-
-                    if (!parts) {
-                        debug('DOMinserts offenders transform function error with "%s"', offender);
-                        return {
-                            parseError: offender
-                        };
-                    }
-
-                    return {
-                        insertedElement: offendersHelpers.domPathToDomElementObj(parts[1]),
-                        receiverElement: offendersHelpers.domPathToDomElementObj(parts[2])
-                    };
-                })
-            };
-        }
     },
     },
-    "DOMqueries": {
-        "tool": "phantomas",
-        "label": "DOM queries",
-        "message": "<p>DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.</p><p>Try to reduce the number of queries by refactoring your JavaScript code.</p><p>Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.</p>",
-        "isOkThreshold": 50,
-        "isBadThreshold": 1000,
-        "isAbnormalThreshold": 2000,
-        "hasOffenders": false
-    },
-    "DOMqueriesWithoutResults": {
-        "tool": "phantomas",
-        "label": "DOM queries without result",
-        "message": "<p>Number of queries that return no result.</p><p>It suggests the query is not used on the page, probably because it is some dead code.</p><p>Or maybe the code is trying to find an HTML block that is not always here. Look at the JS Timeline to see if the scripts correctly figures out the HTML block is not here and immediatly stops interacting further with the DOM.</p>",
+    "queriesWithoutResults": {
+        "tool": "jsExecutionTransformer",
+        "label": "Queries without result",
+        "message": "<p>Number of queries that return no result. Both native and jQuery DOM requests are counted.</p><p>It suggests the query is not used on the page, probably because it is some dead code.</p><p>Or maybe the code is trying to find an HTML block that is not always here. Look at the JS Timeline to see if the scripts correctly figures out the HTML block is not here and immediatly stops interacting further with the DOM.</p>",
         "isOkThreshold": 0,
         "isOkThreshold": 0,
         "isBadThreshold": 100,
         "isBadThreshold": 100,
         "isAbnormalThreshold": 200,
         "isAbnormalThreshold": 200,
-        "hasOffenders": true,
-        "offendersTransformFn": function(offenders) {
-            return {
-                count: offenders.length,
-                list: offenders.map(function(offender) {
-                    var parts = /^(.*) ?\(in ?(.*)\) ?using ?(.*)$/.exec(offender);
-
-                    if (!parts) {
-                        debug('DOMqueriesWithoutResults offenders transform function error with "%s"', offender);
-                        return {
-                            parseError: offender
-                        };
-                    }
-
-                    return {
-                        query: parts[1],
-                        context: offendersHelpers.domPathToDomElementObj(parts[2]),
-                        fn: parts[3]
-                    };
-                })
-            };
-        }
+        "hasOffenders": false
     },
     },
     "DOMqueriesAvoidable": {
     "DOMqueriesAvoidable": {
         "tool": "phantomas",
         "tool": "phantomas",
@@ -175,35 +116,6 @@ var policies = {
             };
             };
         }
         }
     },
     },
-    "eventsBound": {
-        "tool": "phantomas",
-        "label": "Events bound",
-        "message": "<p>Binding too many events has a cost.</p><p>It can be avoided by using \"event delegation\". Instead of binding events on each element one by one, events delegation binds them on the top level document element and uses the bubbling principle. It will imperceptibly slow down the event when it occurs, but the loading of the page will speed-up.</p>",
-        "isOkThreshold": 100,
-        "isBadThreshold": 800,
-        "isAbnormalThreshold": 1500,
-        "hasOffenders": true,
-        "offendersTransformFn": function(offenders) {
-            return {
-                count: offenders.length,
-                list: offenders.map(function(offender) {
-                    var parts = /^"(.*)" ?bound ?to ?"(.*)"$/.exec(offender);
-
-                    if (!parts) {
-                        debug('eventsBound offenders transform function error with "%s"', offender);
-                        return {
-                            parseError: offender
-                        };
-                    }
-
-                    return {
-                        eventName: parts[1],
-                        element: offendersHelpers.domPathToDomElementObj(parts[2])
-                    };
-                })
-            };
-        }
-    },
     "eventsScrollBound": {
     "eventsScrollBound": {
         "tool": "phantomas",
         "tool": "phantomas",
         "label": "Scroll events bound",
         "label": "Scroll events bound",
@@ -351,9 +263,9 @@ var policies = {
         "tool": "phantomas",
         "tool": "phantomas",
         "label": "Global variables",
         "label": "Global variables",
         "message": "<p>It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.</p><p>Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.</p>",
         "message": "<p>It is a bad practice because they clutter up the global namespace. If two scripts use the same variable name in the global scope, it can cause conflicts and it is generally hard to debug.</p><p>Global variables also take a (very) little bit longer to be accessed than variables in the local scope of a function.</p>",
-        "isOkThreshold": 10,
-        "isBadThreshold": 50,
-        "isAbnormalThreshold": 200,
+        "isOkThreshold": 30,
+        "isBadThreshold": 100,
+        "isAbnormalThreshold": 400,
         "hasOffenders": true,
         "hasOffenders": true,
         "offendersTransformFn": function(offenders) {
         "offendersTransformFn": function(offenders) {
             return {
             return {
@@ -389,17 +301,17 @@ var policies = {
                     score = 70;
                     score = 70;
                 } else if (value.indexOf('1.8.') === 0) {
                 } else if (value.indexOf('1.8.') === 0) {
                     score = 50;
                     score = 50;
-                } else if (value.indexOf('1.7.') === 0) {
+                } else if (value.indexOf('1.7') === 0) {
                     score = 40;
                     score = 40;
-                } else if (value.indexOf('1.6.') === 0) {
+                } else if (value.indexOf('1.6') === 0) {
                     score = 30;
                     score = 30;
-                } else if (value.indexOf('1.5.') === 0) {
+                } else if (value.indexOf('1.5') === 0) {
                     score = 20;
                     score = 20;
-                } else if (value.indexOf('1.4.') === 0) {
+                } else if (value.indexOf('1.4') === 0) {
                     score = 10;
                     score = 10;
-                } else if (value.indexOf('1.3.') === 0) {
+                } else if (value.indexOf('1.3') === 0) {
                     score = 0;
                     score = 0;
-                } else if (value.indexOf('1.2.') === 0) {
+                } else if (value.indexOf('1.2') === 0) {
                     score = 0;
                     score = 0;
                 } else {
                 } else {
                     debug('Unknown jQuery version "%s"', value);
                     debug('Unknown jQuery version "%s"', value);
@@ -433,12 +345,21 @@ var policies = {
     "jQueryFunctionsUsed": {
     "jQueryFunctionsUsed": {
         "tool": "jsExecutionTransformer",
         "tool": "jsExecutionTransformer",
         "label": "jQuery usage",
         "label": "jQuery usage",
-        "message": "<p>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.</p><p>If only a few functions are used, why not trying to get rid of jQuery? Have a look at <a href=\"http://youmightnotneedjquery.com/\" target=\"_blank\">http://youmightnotneedjquery.com</a>.</p>",
+        "message": "<p>This is the number of different core jQuery functions called on load. This rule is not trying to blame you for using jQuery too much, but the opposite.</p><p>If only a few functions are used, why not trying to get rid of jQuery? Have a look at <a href=\"http://youmightnotneedjquery.com/\" target=\"_blank\">http://youmightnotneedjquery.com</a>.</p>",
         "isOkThreshold": 15,
         "isOkThreshold": 15,
         "isBadThreshold": 6,
         "isBadThreshold": 6,
         "isAbnormalThreshold": 0,
         "isAbnormalThreshold": 0,
         "hasOffenders": true
         "hasOffenders": true
     },
     },
+    "jQueryCallsOnEmptyObject": {
+        "tool": "jsExecutionTransformer",
+        "label": "Calls on empty objects",
+        "message": "<p>This metric counts the number of jQuery functions called on an empty jQuery object. The call was useless.</p><p>This can be helpful to detect dead or unused code.</p>",
+        "isOkThreshold": 1,
+        "isBadThreshold": 100,
+        "isAbnormalThreshold": 180,
+        "hasOffenders": false
+    },
     "jQueryNotDelegatedEvents": {
     "jQueryNotDelegatedEvents": {
         "tool": "jsExecutionTransformer",
         "tool": "jsExecutionTransformer",
         "label": "Events not delegated",
         "label": "Events not delegated",
@@ -446,7 +367,7 @@ var policies = {
         "isOkThreshold": 1,
         "isOkThreshold": 1,
         "isBadThreshold": 100,
         "isBadThreshold": 100,
         "isAbnormalThreshold": 180,
         "isAbnormalThreshold": 180,
-        "hasOffenders": true
+        "hasOffenders": false
     },
     },
     "cssParsingErrors": {
     "cssParsingErrors": {
         "tool": "phantomas",
         "tool": "phantomas",
@@ -1024,7 +945,7 @@ var policies = {
         "tool": "phantomas",
         "tool": "phantomas",
         "label": "Font count",
         "label": "Font count",
         "message": "<p>Fonts are loaded on the critical path of the head. Load as few as possible.</p>",
         "message": "<p>Fonts are loaded on the critical path of the head. Load as few as possible.</p>",
-        "isOkThreshold": 0,
+        "isOkThreshold": 1,
         "isBadThreshold": 3,
         "isBadThreshold": 3,
         "isAbnormalThreshold": 5,
         "isAbnormalThreshold": 5,
         "hasOffenders": true,
         "hasOffenders": true,

+ 6 - 7
lib/metadata/scoreProfileGeneric.json

@@ -12,11 +12,9 @@
         "domManipulations": {
         "domManipulations": {
             "label": "DOM manipulations",
             "label": "DOM manipulations",
             "policies": {
             "policies": {
-                "DOMinserts": 2,
-                "DOMqueries": 1,
-                "DOMqueriesWithoutResults": 2,
-                "DOMqueriesAvoidable": 2,
-                "eventsBound": 1
+                "DOMaccesses": 3,
+                "queriesWithoutResults": 1,
+                "DOMqueriesAvoidable": 1
             }
             }
         },
         },
         "scroll": {
         "scroll": {
@@ -38,9 +36,10 @@
         "jQuery": {
         "jQuery": {
             "label": "jQuery",
             "label": "jQuery",
             "policies": {
             "policies": {
-                "jQueryVersion": 1,
-                "jQueryVersionsLoaded": 1,
+                "jQueryVersion": 2,
+                "jQueryVersionsLoaded": 2,
                 "jQueryFunctionsUsed": 1,
                 "jQueryFunctionsUsed": 1,
+                "jQueryCallsOnEmptyObject": 1,
                 "jQueryNotDelegatedEvents": 1
                 "jQueryNotDelegatedEvents": 1
             }
             }
         },
         },

+ 6 - 0
lib/server/datastores/runsDatastore.js

@@ -58,6 +58,9 @@ function RunsDatastore() {
 
 
         var errorMessage;
         var errorMessage;
         switch(err) {
         switch(err) {
+            case '1':
+                errorMessage = "Error 1: unknown error";
+                break;
             case '252':
             case '252':
                 errorMessage = "Error 252: page timeout in Phantomas";
                 errorMessage = "Error 252: page timeout in Phantomas";
                 break;
                 break;
@@ -76,6 +79,9 @@ function RunsDatastore() {
             case '1002':
             case '1002':
                 errorMessage = "Error 1002: missing Phantomas metrics";
                 errorMessage = "Error 1002: missing Phantomas metrics";
                 break;
                 break;
+            case '1003':
+                errorMessage = "Error 1003: Phantomas not returning";
+                break;
             default:
             default:
                 errorMessage = err;
                 errorMessage = err;
         }
         }

+ 12 - 0
lib/server/middlewares/wwwRedirectMiddleware.js

@@ -0,0 +1,12 @@
+var wwwRedirectMiddleware = function(req, res, next) {
+    'use strict';
+
+    // Redirect www.yellowlab.tools to yellowlab.tools without "www" (for SEO purpose)
+    if(/^www\.yellowlab\.tools/.test(req.headers.host)) {
+        res.redirect(301, req.protocol + '://' + req.headers.host.replace(/^www\./, '') + req.url);
+    } else {
+        next();
+    }
+};
+
+module.exports = wwwRedirectMiddleware;

+ 23 - 24
lib/tools/jsExecutionTransformer.js

@@ -10,14 +10,17 @@ var jsExecutionTransformer = function() {
         var jQueryFunctionsCollection = new Collection();
         var jQueryFunctionsCollection = new Collection();
         
         
         var metrics = {
         var metrics = {
+            domInteractive: 0,
+            domContentLoaded: 0,
+            domContentLoadedEnd: 0,
+            domComplete: 0,
+
             DOMaccesses: 0,
             DOMaccesses: 0,
             DOMaccessesOnScroll: 0,
             DOMaccessesOnScroll: 0,
             queriesWithoutResults: 0
             queriesWithoutResults: 0
         };
         };
 
 
-        var offenders = {
-
-        };
+        var offenders = {};
 
 
         var hasjQuery = (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded > 0);
         var hasjQuery = (data.toolsResults.phantomas.metrics.jQueryVersionsLoaded > 0);
         if (hasjQuery) {
         if (hasjQuery) {
@@ -27,7 +30,6 @@ var jsExecutionTransformer = function() {
             metrics.jQueryNotDelegatedEvents = 0;
             metrics.jQueryNotDelegatedEvents = 0;
 
 
             offenders.jQueryFunctionsUsed = [];
             offenders.jQueryFunctionsUsed = [];
-            offenders.jQueryNotDelegatedEvents = [];
         }
         }
 
 
         try {
         try {
@@ -42,22 +44,19 @@ var jsExecutionTransformer = function() {
 
 
                     if (isABindWithoutEventDelegation(node, contextLength)) {
                     if (isABindWithoutEventDelegation(node, contextLength)) {
                         metrics.jQueryNotDelegatedEvents += 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.warning = true;
                         node.eventNotDelegated = true;
                         node.eventNotDelegated = true;
                     }
                     }
 
 
                     if (node.data.resultsNumber === 0) {
                     if (node.data.resultsNumber === 0) {
                         metrics.queriesWithoutResults ++;
                         metrics.queriesWithoutResults ++;
+                        node.queryWithoutResults = true;
                         node.warning = true;
                         node.warning = true;
                     }
                     }
 
 
                     if (contextLength === 0) {
                     if (contextLength === 0) {
                         metrics.jQueryCallsOnEmptyObject ++;
                         metrics.jQueryCallsOnEmptyObject ++;
+                        node.jQueryCallOnEmptyObject = true;
                         node.warning = true;
                         node.warning = true;
                     }
                     }
 
 
@@ -74,22 +73,22 @@ var jsExecutionTransformer = function() {
                     // Mark a performance flag
                     // Mark a performance flag
                     if (['domInteractive', 'domContentLoaded', 'domContentLoadedEnd', 'domComplete'].indexOf(node.data.type) >= 0) {
                     if (['domInteractive', 'domContentLoaded', 'domContentLoadedEnd', 'domComplete'].indexOf(node.data.type) >= 0) {
                         node.windowPerformance = true;
                         node.windowPerformance = true;
-                    }
 
 
-                    // Read the execution tree and adjust the navigation timings (cause their not very well synchronised)
-                    switch(node.data.type) {
-                        case 'domInteractive':
-                            data.toolsResults.phantomas.metrics.domInteractive = node.data.timestamp;
-                            break;
-                        case 'domContentLoaded':
-                            data.toolsResults.phantomas.metrics.domContentLoaded = node.data.timestamp;
-                            break;
-                        case 'domContentLoadedEnd':
-                            data.toolsResults.phantomas.metrics.domContentLoadedEnd = node.data.timestamp;
-                            break;
-                        case 'domComplete':
-                            data.toolsResults.phantomas.metrics.domComplete = node.data.timestamp;
-                            break;
+                        // Adjust the navigation timings (cause their not very well synchronised)
+                        switch(node.data.type) {
+                            case 'domInteractive':
+                                javascriptExecutionTree.data.domInteractive = node.data.timestamp;
+                                break;
+                            case 'domContentLoaded':
+                                javascriptExecutionTree.data.domContentLoaded = node.data.timestamp;
+                                break;
+                            case 'domContentLoadedEnd':
+                                javascriptExecutionTree.data.domContentLoadedEnd = node.data.timestamp;
+                                break;
+                            case 'domComplete':
+                                javascriptExecutionTree.data.domComplete = node.data.timestamp;
+                                break;
+                        }
                     }
                     }
 
 
                     // Transform domPaths into objects
                     // Transform domPaths into objects

+ 6 - 1
lib/tools/phantomas/custom_modules/modules/domQYLT/domQYLT.js

@@ -365,7 +365,12 @@ exports.module = function(phantomas) {
         phantomas.log('DOM query: by %s - "%s" (using %s) in %s', type, query, fnName, context);
         phantomas.log('DOM query: by %s - "%s" (using %s) in %s', type, query, fnName, context);
         phantomas.incrMetric('DOMqueries');
         phantomas.incrMetric('DOMqueries');
 
 
-        if (context && context.indexOf('DocumentFragment') === -1) {
+        if (context && (
+                context.indexOf('html') === 0 ||
+                context.indexOf('body') === 0 ||
+                context.indexOf('head') === 0 ||
+                context.indexOf('#document') === 0
+            )) {
             DOMqueries.push(type + ' "' + query + '" with ' + fnName + ' (in context ' + context + ')');
             DOMqueries.push(type + ' "' + query + '" with ' + fnName + ' (in context ' + context + ')');
         }
         }
     });
     });

+ 28 - 9
lib/tools/phantomas/phantomasWrapper.js

@@ -19,6 +19,7 @@ var PhantomasWrapper = function() {
 
 
 
 
         var options = {
         var options = {
+            
             // Cusomizable options
             // Cusomizable options
             'timeout': task.options.timeout || 45,
             'timeout': task.options.timeout || 45,
             'js-deep-analysis': task.options.jsDeepAnalysis || false,
             'js-deep-analysis': task.options.jsDeepAnalysis || false,
@@ -75,34 +76,52 @@ var PhantomasWrapper = function() {
         }
         }
         debug('node node_modules/phantomas/bin/phantomas.js --url=' + task.url + optionsString + ' --verbose');
         debug('node node_modules/phantomas/bin/phantomas.js --url=' + task.url + optionsString + ' --verbose');
 
 
-        // Kill the application if nothing happens
+
         var phantomasPid;
         var phantomasPid;
+        var isKilled = false;
+
+        // Kill phantomas if nothing happens
         var killer = setTimeout(function() {
         var killer = setTimeout(function() {
-            debug('Killing the app because the test on %s was launched %d seconds ago', task.url, 5*options.timeout);
-            // If in server mode, forever will restart the server
+            console.log('Killing phantomas because the test on ' + task.url + ' was launched ' + 5*options.timeout + ' seconds ago');
             
             
-            // Kill the Phantomas process first
             if (phantomasPid) {
             if (phantomasPid) {
                 ps.kill(phantomasPid, function(err) {
                 ps.kill(phantomasPid, function(err) {
+                    
                     if (err) {
                     if (err) {
                         debug('Could not kill Phantomas process %s', phantomasPid);
                         debug('Could not kill Phantomas process %s', phantomasPid);
+
+                        // Suicide
+                        process.exit(1);
+                        // If in server mode, forever will restart the server
                     }
                     }
-                    else {
-                        debug('Phantomas process %s was correctly killed', phantomasPid);
-                    }
 
 
-                    // Then suicide.
-                    process.exit(1);
+                    debug('Phantomas process %s was correctly killed', phantomasPid);
+
+                    // Then mark the test as failed
+                    // Error 1003 = Phantomas not answering
+                    deferred.reject(1003);
+                    isKilled = true;
                 });
                 });
+            } else {
+                // Suicide
+                process.exit(1);
             }
             }
 
 
         }, 5*options.timeout*1000);
         }, 5*options.timeout*1000);
 
 
+
         // It's time to launch the test!!!
         // It's time to launch the test!!!
         var triesNumber = 2;
         var triesNumber = 2;
 
 
         async.retry(triesNumber, function(cb) {
         async.retry(triesNumber, function(cb) {
             var process = phantomas(task.url, options, function(err, json, results) {
             var process = phantomas(task.url, options, function(err, json, results) {
+                
+                if (isKilled) {
+                    debug('Process was killed, too late Phantomas, sorry...');
+                    return;
+                }
+
+
                 debug('Returning from Phantomas');
                 debug('Returning from Phantomas');
 
 
                 // Adding some YellowLabTools errors here
                 // Adding some YellowLabTools errors here

+ 7 - 7
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "yellowlabtools",
   "name": "yellowlabtools",
-  "version": "1.5.1",
+  "version": "1.6.1",
   "description": "Online tool to audit a webpage for performance and front-end quality issues",
   "description": "Online tool to audit a webpage for performance and front-end quality issues",
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",
@@ -11,18 +11,18 @@
   },
   },
   "main": "./lib/index.js",
   "main": "./lib/index.js",
   "dependencies": {
   "dependencies": {
-    "async": "~0.9.0",
+    "async": "~1.0.0",
     "body-parser": "~1.12.4",
     "body-parser": "~1.12.4",
-    "compression": "~1.4.3",
+    "compression": "~1.4.4",
     "cors": "^2.6.0",
     "cors": "^2.6.0",
     "debug": "~2.2.0",
     "debug": "~2.2.0",
-    "express": "~4.12.3",
+    "express": "~4.12.4",
     "lwip": "0.0.6",
     "lwip": "0.0.6",
     "meow": "^3.1.0",
     "meow": "^3.1.0",
     "phantomas": "1.10.2",
     "phantomas": "1.10.2",
     "ps-node": "0.0.4",
     "ps-node": "0.0.4",
-    "q": "~1.4.0",
-    "rimraf": "~2.3.3",
+    "q": "~1.4.1",
+    "rimraf": "~2.3.4",
     "temporary": "0.0.8"
     "temporary": "0.0.8"
   },
   },
   "devDependencies": {
   "devDependencies": {
@@ -47,7 +47,7 @@
     "grunt-usemin": "^3.0.0",
     "grunt-usemin": "^3.0.0",
     "grunt-webfont": "^0.5.3",
     "grunt-webfont": "^0.5.3",
     "matchdep": "^0.3.0",
     "matchdep": "^0.3.0",
-    "mocha": "^2.2.4",
+    "mocha": "^2.2.5",
     "request": "^2.55.0",
     "request": "^2.55.0",
     "sinon": "^1.14.1",
     "sinon": "^1.14.1",
     "sinon-chai": "^2.7.0"
     "sinon-chai": "^2.7.0"

+ 1 - 181
test/core/customPoliciesTest.js

@@ -1,7 +1,7 @@
 var should = require('chai').should();
 var should = require('chai').should();
 var rulesChecker = require('../../lib/rulesChecker');
 var rulesChecker = require('../../lib/rulesChecker');
 
 
-describe('rulesChecker', function() {
+describe('customPolicies', function() {
     
     
     var policies = require('../../lib/metadata/policies.js');
     var policies = require('../../lib/metadata/policies.js');
     var results;
     var results;
@@ -70,131 +70,6 @@ describe('rulesChecker', function() {
         });
         });
     });
     });
 
 
-    
-    it('should transform DOMinserts offenders', function() {
-        results = rulesChecker.check({
-            "toolsResults": {
-                "phantomas": {
-                    "metrics": {
-                        "DOMinserts": 4
-                    },
-                    "offenders": {
-                        "DOMinserts": [
-                            "\"div\" appended to \"html\"",
-                            "\"DocumentFragment > link[0]\" appended to \"head\"",
-                            "\"div#Netaff-yh1XbS0vK3NaRGu\" appended to \"body > div#Global\"",
-                            "\"img\" appended to \"body\""
-                        ]
-                    }
-                }
-            }
-        }, policies);
-
-        results.should.have.a.property('DOMinserts');
-        results.DOMinserts.should.have.a.property('offendersObj').that.deep.equals({
-            "count": 4,
-            "list": [
-                {
-                    "insertedElement": {
-                        "type": "createdElement",
-                        "element": "div"
-                    },
-                    "receiverElement": {
-                        "type": "html"
-                    }
-                },
-                {
-                    "insertedElement": {
-                        "type": "fragmentElement",
-                        "element": "link[0]",
-                        "tree": {
-                            "DocumentFragment": {
-                                "link[0]": 1
-                            }
-                        }
-                    },
-                    "receiverElement": {
-                        "type": "head"
-                    }
-                },
-                {
-                    "insertedElement": {
-                        "type": "createdElement",
-                        "element": "div#Netaff-yh1XbS0vK3NaRGu"
-                    },
-                    "receiverElement": {
-                        "type": "domElement",
-                        "element": "div#Global",
-                        "tree": {
-                            "body": {
-                                "div#Global": 1
-                            }
-                        }
-                    }
-                },
-                {
-                    "insertedElement": {
-                        "type": "createdElement",
-                        "element": "img"
-                    },
-                    "receiverElement": {
-                        "type": "body"
-                    }
-                }
-            ]
-        });
-    });
-
-    
-    it('should transform DOMqueriesWithoutResults offenders', function() {
-        results = rulesChecker.check({
-            "toolsResults": {
-                "phantomas": {
-                    "metrics": {
-                        "DOMqueriesWithoutResults": 2
-                    },
-                    "offenders": {
-                        "DOMqueriesWithoutResults": [
-                            "#SearchMenu (in #document) using getElementById",
-                            ".partnership-link (in body > div#Global > div#Header > ul#MainMenu) using getElementsByClassName"
-                        ]
-                    }
-                }
-            }
-        }, policies);
-
-        results.should.have.a.property('DOMqueriesWithoutResults');
-        results.DOMqueriesWithoutResults.should.have.a.property('offendersObj').that.deep.equals({
-            "count": 2,
-            "list": [
-                {
-                    "context": {
-                        "type": "document"
-                    },
-                    "fn": "getElementById",
-                    "query": "#SearchMenu "
-                },
-                {
-                    "context": {
-                        "element": "ul#MainMenu",
-                        "tree": {
-                            "body": {
-                                "div#Global": {
-                                    "div#Header": {
-                                        "ul#MainMenu": 1
-                                    }
-                                }
-                            }
-                        },
-                        "type": "domElement"
-                    },
-                    "fn": "getElementsByClassName",
-                    "query": ".partnership-link "
-                }
-            ]
-        });
-    });
-
 
 
     it('should transform DOMqueriesAvoidable offenders', function() {
     it('should transform DOMqueriesAvoidable offenders', function() {
         results = rulesChecker.check({
         results = rulesChecker.check({
@@ -246,61 +121,6 @@ describe('rulesChecker', function() {
     });
     });
 
 
 
 
-    it('should transform eventsBound offenders', function() {
-        results = rulesChecker.check({
-            "toolsResults": {
-                "phantomas": {
-                    "metrics": {
-                        "eventsBound": 2
-                    },
-                    "offenders": {
-                        "eventsBound": [
-                            "\"DOMContentLoaded\" bound to \"#document\"",
-                            "\"unload\" bound to \"window\"",
-                            "\"submit\" bound to \"body > div#Global > div#Header > form#search_mini_form\""
-                        ]
-                    }
-                }
-            }
-        }, policies);
-
-        results.should.have.a.property('eventsBound');
-        results.eventsBound.should.have.a.property('offendersObj').that.deep.equals({
-            "count": 3,
-            "list": [
-                {
-                    "element": {
-                        "type": "document"
-                    },
-                    "eventName": "DOMContentLoaded"
-                },
-                {
-                    "element": {
-                        "type": "window"
-                    },
-                    "eventName": "unload"
-                },
-                {
-                    "element": {
-                        "element": "form#search_mini_form",
-                        "tree": {
-                            "body": {
-                                "div#Global": {
-                                    "div#Header": {
-                                        "form#search_mini_form": 1
-                                    }
-                                }
-                            }
-                        },
-                        "type": "domElement"
-                    },
-                    "eventName": "submit"
-                }
-            ]
-        });
-    });
-
-
     it('should transform jsErrors offenders', function() {
     it('should transform jsErrors offenders', function() {
         results = rulesChecker.check({
         results = rulesChecker.check({
             "toolsResults": {
             "toolsResults": {

+ 9 - 3
test/core/indexTest.js

@@ -53,8 +53,9 @@ describe('index.js', function() {
                 data.toolsResults.phantomas.metrics.should.have.a.property('requests').that.equals(1);
                 data.toolsResults.phantomas.metrics.should.have.a.property('requests').that.equals(1);
                 data.toolsResults.phantomas.should.have.a.property('offenders').that.is.an('object');
                 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.should.have.a.property('DOMelementMaxDepth');
-                data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(1);
+                data.toolsResults.phantomas.offenders.DOMelementMaxDepth.should.have.length(2);
                 data.toolsResults.phantomas.offenders.DOMelementMaxDepth[0].should.equal('body > h1[0]');
                 data.toolsResults.phantomas.offenders.DOMelementMaxDepth[0].should.equal('body > h1[0]');
+                data.toolsResults.phantomas.offenders.DOMelementMaxDepth[1].should.equal('body > script[1]');
 
 
                 // Test rules
                 // Test rules
                 data.should.have.a.property('rules').that.is.an('object');
                 data.should.have.a.property('rules').that.is.an('object');
@@ -76,10 +77,11 @@ describe('index.js', function() {
                     "score": 100,
                     "score": 100,
                     "abnormalityScore": 0,
                     "abnormalityScore": 0,
                     "offendersObj": {
                     "offendersObj": {
-                        "count": 1,
+                        "count": 2,
                         "tree": {
                         "tree": {
                             "body": {
                             "body": {
-                                "h1[0]": 1
+                                "h1[0]": 1,
+                                "script[1]": 1
                             }
                             }
                         }
                         }
                     }
                     }
@@ -91,6 +93,10 @@ describe('index.js', function() {
                 data.should.have.a.property('javascriptExecutionTree').that.is.an('object');
                 data.should.have.a.property('javascriptExecutionTree').that.is.an('object');
                 data.javascriptExecutionTree.should.have.a.property('data');
                 data.javascriptExecutionTree.should.have.a.property('data');
                 data.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
                 data.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
+                data.javascriptExecutionTree.data.should.have.a.property('domInteractive').that.is.a('number');
+                data.javascriptExecutionTree.data.should.have.a.property('domContentLoaded').that.is.a('number');
+                data.javascriptExecutionTree.data.should.have.a.property('domContentLoadedEnd').that.is.a('number');
+                data.javascriptExecutionTree.data.should.have.a.property('domComplete').that.is.a('number');
 
 
                 /*jshint expr: true*/
                 /*jshint expr: true*/
                 console.log.should.not.have.been.called;
                 console.log.should.not.have.been.called;

+ 4 - 0
test/www/simple-page.html

@@ -4,5 +4,9 @@
 </head>
 </head>
 <body>
 <body>
     <h1>Simple page</h1>
     <h1>Simple page</h1>
+
+    <script>
+        document.getElementById('foo');
+    </script>
 </body>
 </body>
 </html>
 </html>

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels