Closed Bug 333198 Opened 18 years ago Closed 15 years ago

Suppress Input events for web content during synchronous XMLHttpRequest loads

Categories

(Core :: XML, defect, P1)

defect

Tracking

()

RESOLVED FIXED
mozilla1.9.2a1

People

(Reporter: darin.moz, Assigned: smaug)

References

Details

(Keywords: fixed1.9.1, Whiteboard: [fixed by bug 480767])

Attachments

(6 files, 8 obsolete files)

1.87 KB, text/html
Details
45.14 KB, patch
bzbarsky
: review+
Details | Diff | Splinter Review
45.15 KB, patch
Details | Diff | Splinter Review
44.74 KB, patch
Details | Diff | Splinter Review
46.08 KB, patch
Details | Diff | Splinter Review
30.57 KB, patch
Details | Diff | Splinter Review
Suppress Input events for web content during synchronous loads
Status: NEW → ASSIGNED
Priority: -- → P1
Target Milestone: --- → mozilla1.9alpha
-> reassign to default owner
Assignee: darin.moz → nobody
Status: ASSIGNED → NEW
Priority: P1 → --
Target Milestone: mozilla1.9alpha1 → ---
Summary: Suppress Input events for web content during synchronous loads → Suppress Input events for web content during synchronous XMLHttpRequest loads
See also bug 340345, DOM timeouts can fire during a sync XMLHttpRequest.
From bug 340345 it seems this should be assigned and marked a blocker too.

/be
Flags: blocking1.9.1?
Assignee: nobody → Olli.Pettay
Blocking, though weakly. Smaug: please update this bug with an ETA and risk assessment ASAP?
Flags: blocking1.9.1? → blocking1.9.1+
Priority: -- → P2
I'll try to write the patch tomorrow.
Attached patch patch (obsolete) — Splinter Review
The nsXMLHttpRequest::Send parts of the patch require bug 340345,
but since that patch isn't quite ready, this needs to wait a bit too.

This includes fix to not dispatch progress events when XHR is synchronous.
Is that really the right thing to do with progress events I can see wanting progress events in a worker to update a progress bar on the UI thread, no?  What does the spec say here?

What about onReadyStateChange events?  See bug 313646.
It is the right thing per the spec.
If worker thread wants to update UI, don't use sync XHR.

Actually, is worker thread ever using sync XHR internally?
And readystatechange is a diffent thing. It is a legacy event.
Depends on: 340345
Ah, ok.  Carry on, then.  ;)
I need to handle focus/blur somehow.
Better patch coming ...
Attached patch v2 with blur handling (obsolete) — Splinter Review
Depends on bug 340345.
Attachment #361770 - Attachment is obsolete: true
Hmm, where did one version of the patch go? Perhaps accidentally to some
wrong bug.

Anyway, this is tested when focus moves between moz windows, or moz and non-moz
windows or between chrome and content.
Attachment #362118 - Flags: review?(jst)
Attached file Something for manual testing (obsolete) —
Attachment #362118 - Flags: superreview+
Attachment #362118 - Flags: review?(jst)
Attachment #362118 - Flags: review?(bzbarsky)
Attachment #362118 - Flags: review+
Comment on attachment 362118 [details] [diff] [review]
v2 with blur handling

I think this looks good. Please go ahead and land this on the trunk asap. I'd like bz to look this over once too before we take this for 1.9.1, but getting mileage on this on the trunk ASAP is very valuable, so feel free to land on trunk before bz has a chance to review.
Some questions:

1) Why suppress on only the primary shell, not on all shells?  It's probably ok,
   but just checking.
2) Why can't EventHandlingSuppressed() just be passed the relevant prescontext?
   Or does gLastFocusedPresContextWeak possibly not match gLastFocusedDocument?
3) If the script happens to reframe a subframe of the document during the sync
   load (e.g. off a onreadystatechange callback), that presshell won't have
   event handling suppressed as far as I can tell.  That should probably be
   fixed.
4) UnsuppressEventHandling() should probably call MaybeFireSuppressedEvents()
   on |this| and should be calling UnsuppressEventHandling() on the kids.  The
   current code just looks wrong.
(In reply to comment #20)
> Some questions:
> 
> 1) Why suppress on only the primary shell, not on all shells?  It's probably
> ok,
>    but just checking.
Because I don't care printing here and print preview isn't really interactive
(excluding scrolling).

> 2) Why can't EventHandlingSuppressed() just be passed the relevant prescontext?
>    Or does gLastFocusedPresContextWeak possibly not match gLastFocusedDocument?

I don't understand the first question and the answer to the second one is yes.

> 3) If the script happens to reframe a subframe of the document during the sync
>    load (e.g. off a onreadystatechange callback), that presshell won't have
>    event handling suppressed as far as I can tell.  That should probably be
>    fixed.
Indeed. When presshell is created it should inherit its parent shell's
event suppression.


> 4) UnsuppressEventHandling() should probably call MaybeFireSuppressedEvents()
>    on |this| and should be calling UnsuppressEventHandling() on the kids.  The
>    current code just looks wrong.
Why does it look wrong? I'm trying to avoid the case when calling 
EnumerateSubDocuments could add or remove sub documents.
(In reply to comment #20)
> 3) If the script happens to reframe a subframe of the document during the sync
>    load (e.g. off a onreadystatechange callback), that presshell won't have
>    event handling suppressed as far as I can tell.  That should probably be
>    fixed.
Btw,  we don't fire readystatechange events, and with this patch progress events either during sync load.
(In reply to comment #21)
> (In reply to comment #20)
> > Some questions:
> > 
> > 1) Why suppress on only the primary shell, not on all shells?  It's probably
> > ok,
> >    but just checking.
> Because I don't care printing here and print preview isn't really interactive
> (excluding scrolling).
And scripts are disabled when print preview is active.
> Btw,  we don't fire readystatechange events,

Yes, but we need to fix that.

> Why does it look wrong? 

Say XHR calls UnsuppressEventHandling on the top shell.  I'd missed that we add this top shell to the array manually, so that's ok.  Right now that will:

1) Call MaybeFireSuppressedEvents on the top shell.
2) Call MaybeFireSuppressedEvents on all the kids of the top shell.
3) Do nothing with grandkids and lower descendants of the top shell.

That looks wrong because it seems like we should enter MaybeFireSuppressedEvents() for every presshell for which we entered SuppressEventHandling(), if only so that we'll decrement the counter.  The current patch permanently disables events in grandkids, as far as I can tell.  That should even be mochitestable.

Perhaps GetSubShells should recurse or something?  Or better yet we should walk down our array as we build it, adding further subshells to the list.
Oh, as far as multiple presshells my point is we shouldn't assume that it's only used for printing/print-preview, unless we made some arch decision I missed...
(In reply to comment #24)
> 1) Call MaybeFireSuppressedEvents on the top shell.
> 2) Call MaybeFireSuppressedEvents on all the kids of the top shell.
> 3) Do nothing with grandkids and lower descendants of the top shell.
Ah, indeed.

> Perhaps GetSubShells should recurse or something?
That would be the easiest way, I guess.
Attached file for testing
Attachment #362127 - Attachment is obsolete: true
Attached patch better (obsolete) — Splinter Review
suppresses all presshells and handles bfcache and new iframes etc.
But, I realized there is a problem with plugins.
Attachment #362118 - Attachment is obsolete: true
Attachment #362118 - Flags: review?(bzbarsky)
About plugins, see also Bug 340345 comment 79
Chrome blocks page UI etc, but doesn't block plugin UI.
So since Opera and Chrome don't block plugins' UI, perhaps we don't have to either.
Comment on attachment 362381 [details] [diff] [review]
better

Or not this particular patch, but something I'm about to upload.
Attachment #362381 - Attachment is obsolete: true
Comment on attachment 362381 [details] [diff] [review]
better

Er, this is the one after all.
Boris, what you think about this? Moved event suppression counter to document so that all the presshells can check it.
Attachment #362381 - Attachment is obsolete: false
Attachment #362381 - Flags: review?(bzbarsky)
Comment on attachment 362381 [details] [diff] [review]
better

Bah, this was wrong after all. Missing the relevant Suppress call.
Attachment #362381 - Flags: review?(bzbarsky)
Attached patch this one (obsolete) — Splinter Review
Attachment #362381 - Attachment is obsolete: true
Attachment #362564 - Flags: review?(bzbarsky)
I took a quick look at the patch and I noticed the following:

(Correct me if I am wrong ;) )

In PresShell::HandleEvent most events are not suspended but suppressed(keyup/keydown etc.), I think they should be delayed i.s.o. ignored (as in IE /chrome etc.)

further more I noticed that
suspendedDoc->UnsuppressEventHandling() is called in nsXMLHttpRequest::Send 

UnsuppressEventHandling() finally calls FireDelayedEvents, so that means that events (focus/blur) are still fired during a sync XHR.
I think they should be called when the calling javascript is completed (as in IE /chrome etc.)

I more or less expected an event queue for delayed events but I couldn't find it.
(In reply to comment #37)
> In PresShell::HandleEvent most events are not suspended but
> suppressed(keyup/keydown etc.), I think they should be delayed i.s.o. ignored
> (as in IE /chrome etc.)
This bug is "Suppress Input events for web content during synchronous XMLHttpRequest loads".

> UnsuppressEventHandling() finally calls FireDelayedEvents, so that means that
> events (focus/blur) are still fired during a sync XHR.
> I think they should be called when the calling javascript is completed (as in
> IE /chrome etc.)
Well, Chrome, Safari, and Opera fire blur after load, and so does the patch.
But sure, firing blur could be delayed to happen after send() returns.
(In reply to comment #38)
> (In reply to comment #37)
> > In PresShell::HandleEvent most events are not suspended but
> > suppressed(keyup/keydown etc.), I think they should be delayed i.s.o. ignored
> > (as in IE /chrome etc.)
> This bug is "Suppress Input events for web content during synchronous
> XMLHttpRequest loads".
> 

I see. But Chrome, IE etc, do suspend the events. I also think that is what a user expects, now events are 'eaten' by sync XHR.

> > UnsuppressEventHandling() finally calls FireDelayedEvents, so that means that
> > events (focus/blur) are still fired during a sync XHR.
> > I think they should be called when the calling javascript is completed (as in
> > IE /chrome etc.)
> Well, Chrome, Safari, and Opera fire blur after load, and so does the patch.
> But sure, firing blur could be delayed to happen after send() returns.

That is not what I am trying to tell:
in the following function the blur/focus event can be fired between sendSync1 and sendSync2(when javascript has not run to completion yet).
function doTwoSyncXHR() {
sendSync1
sendSync2
}
In chrome/IE etc. they fire when javascript has run to completion. That's what a javascript developer expects (I think).
(In reply to comment #39)
> I see. But Chrome, IE etc, do suspend the events. I also think that is what a
> user expects, now events are 'eaten' by sync XHR.
For 1.9.1 suspending events isn't easily doable, IMO.
And it would need more testing, like what do IE/Chrome do if readystatechanged listeners change focused element?

> In chrome/IE etc. they fire when javascript has run to completion. That's what
> a javascript developer expects (I think).
Right. And I'm just about to upload an updated patch which brings the behavior you want ;)
Attached patch async unsuppress (obsolete) — Splinter Review
Attachment #362749 - Flags: review?(bzbarsky)
For testing I've used for example
http://mozilla.pettay.fi/xhr_upload/xhr_upload_demo_sync.html , but note, I may
edit that test at any time.
I'm not at all convinced that we want to suspend rather than suppress. When I'm interacting with an app and clicking something does not immediately respond I tend to click around a few things to see if it's just that function that is broken, or if the whole app is. If all those clicks come crashing down on me in a rapid succession a bit later that is unlikely what I want.

Especially since things might move around which cause the clicks that I did turn into clicks on completely unrelated buttons. With consequences possibly as bad as data loss.

Things are possibly not as bad for keyboard events since at least they will be targetting the same element as the UI indicated, i.e. the focused element. But things like enter and tab can cause just as bad things to happen.

If we want to talk about suspending, lets do that in a different bug. Especially since we need to minimize risk here, given that this patch is going in so late in the game.
(In reply to comment #43)
> I'm not at all convinced that we want to suspend rather than suppress. When I'm
> interacting with an app and clicking something does not immediately respond I
> tend to click around a few things to see if it's just that function that is
> broken, or if the whole app is. If all those clicks come crashing down on me in
> a rapid succession a bit later that is unlikely what I want.
> 
> Especially since things might move around which cause the clicks that I did
> turn into clicks on completely unrelated buttons. With consequences possibly as
> bad as data loss.
> 
> Things are possibly not as bad for keyboard events since at least they will be
> targetting the same element as the UI indicated, i.e. the focused element. But
> things like enter and tab can cause just as bad things to happen.
> 
> If we want to talk about suspending, lets do that in a different bug.
> Especially since we need to minimize risk here, given that this patch is going
> in so late in the game.

I agree for this stage.
Attachment #362564 - Attachment is obsolete: true
Attachment #362564 - Flags: review?(bzbarsky)
Attachment #362749 - Flags: superreview?(jst)
Comment on attachment 362749 [details] [diff] [review]
async unsuppress

- In nsXMLHttpRequest::Send():

+    if (suspendedDoc) {
+      NS_DispatchToCurrentThread(
+        NS_NEW_RUNNABLE_METHOD(nsIDocument, suspendedDoc,
+                               UnsuppressEventHandling));
+    }

Do we need to worry about the case where someone does in script:

  xhr1.send(sync=true)
  alert("foo")

In that case we'll fire the suppressed events when the modal dialog opens, I think.
smaug, any chance of an interdiff here?  It seems like that would be a lot easier to review.... and bugzilla's refusing to provide one.
(In reply to comment #45) 
> In that case we'll fire the suppressed events when the modal dialog opens, I
> think.
Ah right. Tricky. I wonder if alert should suppress events too.

(In reply to comment #46)
> smaug, any chance of an interdiff here?
I'm not sure what kind of interdiff would help with reviewing.
(In reply to comment #47)
> (In reply to comment #45) 
> > In that case we'll fire the suppressed events when the modal dialog opens, I
> > think.
> Ah right. Tricky. I wonder if alert should suppress events too.
Or perhaps not. I could add some stack object to automatically suppress UI events
while alert/confirm/prompt/any-modal-window is open.
Need to figure out the right place for that, so that embedding doesn't break.
> I'm not sure what kind of interdiff would help with reviewing.

One between the patch I already reviewed and the current one, unless it's just all different...
Comment on attachment 362749 [details] [diff] [review]
async unsuppress

I'm about to upload a new patch which handles the alert case too.
Attachment #362749 - Attachment is obsolete: true
Attachment #362749 - Flags: superreview?(jst)
Attachment #362749 - Flags: review?(bzbarsky)
Attached patch +handels modal dialogs (obsolete) — Splinter Review
Attachment #363350 - Flags: superreview?(jst)
Attachment #363350 - Flags: review?(bzbarsky)
Wow, I've been waiting years for modals to block events. We lost Netscape parity  (IE and other browsers too) way back when (there may be a bug on it to dup fwd).

/be
I *think* that this doesn't block *all* events. Things like XHR events and pending <script>s will still fire. Same thing for localStorage and database callbacks once we have those.

But I think this patch covers 95% of the important cases. And we can always block the other ones on a case by case basis if it becomes important.
nsIThreadInternal.pushEventQueue is *almost* what you want here, as far as I can tell, except that it doesn't filter/run already-pending events, only new events.  That seems on its face like it could be a reasonably small addition to nsIThreadInternal to push a filter that could recognize content-generated script events and queue them up until whatever blocking event (sync XHR, modal dialog, etc.) completed.  Post-191 material in any case if this idea is even implementable, threads and our use of them being what they are...
Comment on attachment 363350 [details] [diff] [review]
+handels modal dialogs

Yet another small change coming. I'll move the nsDocumentViewer change to a better place.
Attachment #363350 - Flags: superreview?(jst)
Attachment #363350 - Flags: review?(bzbarsky)
Attached patch v8Splinter Review
Attachment #363350 - Attachment is obsolete: true
Attachment #363691 - Flags: superreview?(jst)
Attachment #363691 - Flags: review?(bzbarsky)
Pushing an event queue will wreak havoc with existing in-progress necko loads and the like.  It's not to be done lightly.
Whiteboard: [needs review]
Blocks: 479490
Comment on attachment 363691 [details] [diff] [review]
v8

>+++ b/content/base/public/nsIDocument.h
>+   * Prevents user initiated events to be dispatched to the document and

 ... events from being dispatched ...

Looks good otherwise!  r=bzbarsky
Attachment #363691 - Flags: review?(bzbarsky) → review+
Attachment #363691 - Flags: superreview?(jst) → superreview+
It was pushed by someone
http://hg.mozilla.org/mozilla-central/rev/7184f7d69ff0

I pushed a followup fix for Solaris bustage
http://hg.mozilla.org/mozilla-central/rev/84a3bd5c760a
I'll push this to 1.9.1
Status: NEW → RESOLVED
Closed: 15 years ago
Resolution: --- → FIXED
Whiteboard: [needs review]
Attached patch for 1.9.1 (obsolete) — Splinter Review
Attachment #364513 - Attachment is obsolete: true
Attached patch for 1.9.1Splinter Review
Smaug, please use the fixed1.9.1 keyword when checking in patches on 1.9.1 branch. Thanks.
Keywords: fixed1.9.1
Target Milestone: --- → mozilla1.9.2a1
Depends on: 480768
Depends on: 480767
Keywords: fixed1.9.1
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
No longer depends on: 480972
Marking this a P1, as it blocks P1 blocker bug 479490.
Priority: P2 → P1
Target Milestone: mozilla1.9.2a1 → mozilla1.9.1b3
Whiteboard: [needs bug 480767]
http://hg.mozilla.org/mozilla-central/rev/b095eaf3e8b4
Status: REOPENED → RESOLVED
Closed: 15 years ago15 years ago
Resolution: --- → FIXED
Whiteboard: [needs bug 480767] → [needs landing][needs bug 480767]
So it was backed out on trunk and 1.9.1 too. Will the initial issue be fixed on another bug?
Whiteboard: [needs landing][needs bug 480767] → [needs bug 480767]
This was backed out and then relanded with the fix for bug 480767.
Thanks Smaug. Can we get an automated test for this fix?
Flags: in-testsuite?
Whiteboard: [needs bug 480767] → [fixed by bug 480767]
Target Milestone: mozilla1.9.1b3 → mozilla1.9.2a1
I performed a test for bug 491571 (marked as duplicate of this bug) on Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4.

The behavior is still different from Safari, Chrome, IE, etc. FireFox is suppressing events, which may cause typographical errors (data loss?) on text input and text areas. I think the correct behavior would be queueing such events. There's a test case on bug 491571 for this.

About alert or any open-dialog-box from the event handler, after XHR send(): Yeah, in this case the queued events must be suppressed. Windows has PeekMessage with PM_REMOVE|PM_QS_INPUT, but I don't know how do you wrap Windows.
(In reply to comment #75)
 > The behavior is still different from Safari, Chrome, IE, etc. 
FYI, all the browsers do this in a bit different way. Even Safari and Chrome
behave differently for example when alert() or plugins are used.
(In reply to comment #76)
> FYI, all the browsers do this in a bit different way. Even Safari and Chrome
> behave differently for example when alert() or plugins are used.

I'm just worried about the data-loss issue that can arise from quietly suppressing keyboard events. Imagine a user typing a memo in a text area. While he/she types, the client must make some requests to the server, and because we only target fast servers on fast connections, we decided to use synchronous XHR. Suppressing keyboard events might produce typos that will be stored and perceived too late, breaking future text searches or even causing more serious problems.

As JavaScript developers, this issue will force us to switch from synchronous to asynchronous, which greatly increases complexity as we need to code event queues and event flushing in JavaScript. Although this will work on any browser, only FireFox is forcing us to do this, from the list of browsers we wish to support.

IMHO, keyboard events must never be suppressed due to slow response (which seems to be the case). Most people are somehow aware of a "keyboard buffer", and so, during slow-response periods, the user simply waits for typed letters to appear. Also, most people accept loosing key presses when the system breaks the typing flow, such as when showing a dialog box.

Well, that's just an opinion from a somewhat (12 yrs) experienced UI developer...
FYI, at least on OSX Safari does not queue keyboard events when sync XHR is used.
Depends on: 493251
(In reply to comment #78)
> FYI, at least on OSX Safari does not queue keyboard events when sync XHR is
> used.

Which version o OSX / Safari are you mentioning? We tested testcase of bug 491571 on:

Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; pt-br) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/3.2.1 Safari/525.27.1

And it works like Safari on Windows, IE, Chrome, etc: all keyboard events are queued (none is lost) during a sync XHR. A think mouse events are queued too, and it doesn't seem to be related to sync XHR. It seems that while JavaScript code is running, user events on that page are just queued, like an ordinary single-threaded GUI application. Because a sync XHR blocks the thread until the server responds or timeout, events are naturally queued there.

Hence, handling of all events are done in an ordered, serialized way. No event is suppressed and there is no context-switching, even during blocking operations. IMHO, this enhances robustness and reliability of complex pages. If the page author wants to deal with server slowness, there's the option of using async XHR.
I was testing some nightly Webkit+Safari. And btw, at least Safari
breaks keydown/press/up order when used with sync XHR.

If you want event queue, please open a new bug (and CC me).
This bug is about suppression.
Depends on: 498530
Depends on: 1292502
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: