From 3a47e719d4dd56d22e138049439a825db1c3d68b Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Sun, 14 Jul 2013 01:51:39 -0400 Subject: [PATCH 1/2] Make pads with long lines usable with chrome. - Do not use incorpIfQuick on keyup because it's not ever quick on chrome. Calling incorpIfQuick calls incorporateUserChanges which sets a flag on the current callstack state indicating that the selection has changed (since something was typed). Whenever this flag is set, the event handler will run code to update the selection (and possibly scroll the view as well), which is a very costly operation in webkit browsers. Instead let the user changes be incorporated by the idle worker, scheduling it to run ASAP on keyup. This isn't a perfect solution, but may make pads running on webkit browsers more usable with otherwise fairly unnoticeable changes in the UI. --- src/static/js/ace2_inner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 97540191a..f53e9de8e 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3855,7 +3855,7 @@ function Ace2Inner(){ } else if (type == "keyup") { - var wait = 200; + var wait = 0; idleWorkTimer.atLeast(wait); idleWorkTimer.atMost(wait); } @@ -3875,7 +3875,7 @@ function Ace2Inner(){ if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!inInternationalComposition)) { - if (type != "keyup" || !incorpIfQuick()) + if (type != "keyup") { observeChangesAroundSelection(); } From 5688350bf18322821f11941b57668fb14793f82a Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Sun, 14 Jul 2013 16:46:12 -0400 Subject: [PATCH 2/2] Adapt and add slowness test from @JohnMcLear. - The test now ensures that all three key events are fired when sending keys. Previously, only the 'keypress' event was sent, which failed to trigger very slow code on webkit browsers (as it is triggered by 'keyup'). All three events should really be sent whenever sending keys to the browser to ensure that we're adequately testing real behavior. See the 'sendkeys' plugin for more; it only sends 'keypress'. --- tests/frontend/specs/responsiveness.js | 80 ++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/frontend/specs/responsiveness.js diff --git a/tests/frontend/specs/responsiveness.js b/tests/frontend/specs/responsiveness.js new file mode 100644 index 000000000..44bdd6111 --- /dev/null +++ b/tests/frontend/specs/responsiveness.js @@ -0,0 +1,80 @@ +// Test for https://github.com/ether/etherpad-lite/issues/1763 + +// This test fails in Opera, IE and Safari +// Opera fails due to a weird way of handling the order of execution, yet actual performance seems fine +// Safari fails due the delay being too great yet the actual performance seems fine +// Firefox might panic that the script is taking too long so will fail +// IE will fail due to running out of memory as it can't fit 2M chars in memory. + +// Just FYI Google Docs crashes on large docs whilst trying to Save, it's likely the limitations we are +// experiencing are more to do with browser limitations than improper implementation. +// A ueber fix for this would be to have a separate lower cpu priority thread that handles operations that aren't +// visible to the user. + +// Adapted from John McLear's original test case. + +describe('Responsiveness of Editor', function() { + // create a new pad before each test run + beforeEach(function(cb) { + helper.newPad(cb); + this.timeout(6000); + }); + it('Fast response to keypress in pad with large amount of contents', function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var chars = '0000000000'; // row of placeholder chars + var amount = 200000; //number of blocks of chars we will insert + var length = (amount * (chars.length) +1); // include a counter for each space + var text = ''; // the text we're gonna insert + this.timeout(amount * 100); + + // get keys to send + var keyMultiplier = 10; // multiplier * 10 == total number of key events + var keysToSend = ''; + for(var i=0; i <= keyMultiplier; i++) { + keysToSend += chars; + } + + var textElement = inner$('div'); + textElement.sendkeys('{selectall}'); // select all + textElement.sendkeys('{del}'); // clear the pad text + + for(var i=0; i <= amount; i++) { + text = text + chars + ' '; // add the chars and space to the text contents + } + inner$('div').first().text(text); // Put the text contents into the pad + + helper.waitFor(function(){ // Wait for the new contents to be on the pad + return inner$('div').text().length > length; + }).done(function(){ + + expect( inner$('div').text().length ).to.be.greaterThan( length ); // has the text changed? + var start = new Date().getTime(); // get the start time + + // send some new text to the screen (ensure all 3 key events are sent) + var el = inner$('div').first(); + for(var i = 0; i < keysToSend.length; ++i) { + var x = keysToSend.charCodeAt(i); + ['keyup', 'keypress', 'keydown'].forEach(function(type) { + var e = $.Event(type); + e.keyCode = x; + el.trigger(e); + }); + } + + helper.waitFor(function(){ // Wait for the ability to process + return true; // Ghetto but works for now + }).done(function(){ + var end = new Date().getTime(); // get the current time + var delay = end - start; // get the delay as the current time minus the start time + + console.log('delay:', delay); + expect(delay).to.be.below(200); + done(); + }, 1000); + + }, 10000); + }); + +}); +