From f7da653e874fe5aebc9c68a998c602faef280bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20M=C3=A9tais?= Date: Wed, 6 May 2015 22:45:03 +0200 Subject: [PATCH 1/2] New rules: eventsScrollBound & DOMaccessesOnScroll --- front/src/css/rule.css | 36 +++++--- .../src/js/directives/offendersDirectives.js | 33 ++++++- front/src/less/rule.less | 6 +- front/src/views/rule.html | 27 ++++++ front/src/views/timeline.html | 10 +-- lib/metadata/policies.js | 51 +++++++++++ lib/metadata/scoreProfileGeneric.json | 9 ++ lib/offendersHelpers.js | 2 +- lib/runner.js | 2 + lib/server/controllers/apiController.js | 1 + lib/tools/jsExecutionTransformer.js | 89 ++++++++++++++----- .../custom_modules/core/scopeYLT/scopeYLT.js | 11 ++- .../modules/jsTreeYLT/jsTreeYLT.js | 4 +- .../modules/scrollListener/scrollListener.js | 75 ++++++++++++++++ test/core/offendersHelpersTest.js | 34 +++++++ 15 files changed, 344 insertions(+), 46 deletions(-) create mode 100644 lib/tools/phantomas/custom_modules/modules/scrollListener/scrollListener.js diff --git a/front/src/css/rule.css b/front/src/css/rule.css index 26b009b..0903840 100644 --- a/front/src/css/rule.css +++ b/front/src/css/rule.css @@ -82,7 +82,11 @@ font-size: 3em; margin-bottom: 1em; } -.offenders .offenderButton { +.rule .startTime { + display: none; +} +.offendersTable .offenderButton, +.value .offenderButton { display: inline-block; position: relative; background: #efe; @@ -91,16 +95,19 @@ border-radius: 0.4em; z-index: 1; } -.offenders .offenderButton.opens { +.offendersTable .offenderButton.opens, +.value .offenderButton.opens { padding-right: 0.75em; } -.offenders .offenderButton.opens:after { +.offendersTable .offenderButton.opens:after, +.value .offenderButton.opens:after { position: relative; left: 0.5em; content: '\25BC'; font-size: 0.8em; } -.offenders .offenderButton > div { +.offendersTable .offenderButton > div, +.value .offenderButton > div { display: none; position: absolute; right: 0; @@ -111,28 +118,35 @@ border-top: 1px solid #999; z-index: 2; } -.offenders .offenderButton .domTree { +.offendersTable .offenderButton .domTree, +.value .offenderButton .domTree { text-align: left; white-space: nowrap; } -.offenders .offenderButton .domTree > div { +.offendersTable .offenderButton .domTree > div, +.value .offenderButton .domTree > div { margin: 0.5em; } -.offenders .offenderButton .domTree > div div { +.offendersTable .offenderButton .domTree > div div, +.value .offenderButton .domTree > div div { margin-left: 1em; } -.offenders .offenderButton .backtrace, -.offenders .offenderButton .cssFileAndLine { +.offendersTable .offenderButton .backtrace, +.value .offenderButton .backtrace, +.offendersTable .offenderButton .cssFileAndLine, +.value .offenderButton .cssFileAndLine { white-space: nowrap; padding: 0.5em; } -.offenders .offenderButton.opens:hover { +.offendersTable .offenderButton.opens:hover, +.value .offenderButton.opens:hover { border-bottom-left-radius: 0; border-bottom-right-radius: 0; background: #ffe0cc; z-index: 2; } -.offenders .offenderButton.opens:hover > div { +.offendersTable .offenderButton.opens:hover > div, +.value .offenderButton.opens:hover > div { display: block; background: #ffe0cc; } diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js index f226bb7..c22eed8 100644 --- a/front/src/js/directives/offendersDirectives.js +++ b/front/src/js/directives/offendersDirectives.js @@ -119,6 +119,22 @@ function getNonJQueryHTML(node, onASingleLine) { var type = node.data.type; + if (node.windowPerformance) { + switch (type) { + case 'documentScroll': + return '(triggering the scroll event on document)'; + + case 'windowScroll': + return '(triggering the scroll event on window)'; + + case 'window.onscroll': + return '(calling the window.onscroll function)'; + + default: + return ''; + } + } + if (!node.data.callDetails) { return ''; } @@ -153,6 +169,18 @@ case 'error': return args[0]; + case 'jQuery - onDOMReady': + return '(function)'; + + case 'documentScroll': + return 'The scroll event just triggered on document'; + + case 'windowScroll': + return 'The scroll event just triggered on window'; + + case 'window.onscroll': + return 'The window.onscroll function just got called'; + default: return ''; } @@ -518,9 +546,6 @@ } break; - case 'jQuery - onDOMReady': - return '(function)'; - default: return ''; } @@ -674,7 +699,7 @@ function getProfilerLineHTML(index, node) { return '
' + (index + 1) + '
' + '
' + node.data.type + (node.children ? '
' + recursiveChildrenHTML(node) + '
' : '') + '
' + - '
' + getTimelineParamsHTML(node, false) + '
' + + '
' + getTimelineParamsHTML(node, false) + '
' + '
' + getTimelineDetailsHTML(node) + '
' + '
' + numberWithCommas(node.data.timestamp, 0) + ' ms
'; } diff --git a/front/src/less/rule.less b/front/src/less/rule.less index 8506d1c..b0aa189 100644 --- a/front/src/less/rule.less +++ b/front/src/less/rule.less @@ -90,7 +90,11 @@ } } -.offenders { +.rule .startTime { + display: none; +} + +.offendersTable, .value { .offenderButton { display: inline-block; position: relative; diff --git a/front/src/views/rule.html b/front/src/views/rule.html index fcc6ae0..a31efa4 100644 --- a/front/src/views/rule.html +++ b/front/src/views/rule.html @@ -47,6 +47,20 @@ {{offender.eventName}} bound to +
+
no backtrace
+
+ backtrace +
+
+ {{obj.functionName}}() + + line {{obj.line}} +
+
+
+
+
{{offender.error}}
no backtrace
@@ -158,6 +172,19 @@
+
+

The table below shows the interactions between the JavaScript and the DOM on a scroll event.

+
+
+
+
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 2/2] 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