Closed Bug 264245 Opened 21 years ago Closed 21 years ago

form popup (combobox) appears in wrong place after scroll

Categories

(Core :: Web Painting, defect)

x86
Linux
defect
Not set
normal

Tracking

()

RESOLVED FIXED

People

(Reporter: bzbarsky, Assigned: roc)

References

()

Details

(Keywords: regression)

Attachments

(7 files, 1 obsolete file)

BUILD: 2004-10-12-07 and later Linux builds STEPS TO REPRODUCE: 1) Load https://bugzilla.mozilla.org/query.cgi 2) Scroll down until the "Sort results by:" combobox first appears in the viewport. 3) Click on this combobox to drop down its dropdown. 4) Click away from the combobox to close the dropdown 5) Scroll all the way to the bottom of the page (use the scrollbar, or click twice in step 4 to move focus). 6) Click on the same combobox as in step 3. EXPECTED RESULTS: dropdown appears near the new combobox position ACTUAL RESULTS: dropdown appears at the same place as the first time, so away from the combobox. NOTES: Other comboboxes on that page reproduce the problem too, but this one does it most reliably. Based on when it appeared, this is a regression from bug 238493. It looks very similar to bug 64902, but doesn't need an iframe to reproduce. Given the issues I ran into in bug 264231, it almost feels like ProcessPendingUpdates() is not getting to the popup's view... (which is floating, but that shouldn't affect things). Also, I'm not sure about all the flags being passed to ReflowChild and FinishReflowChild by nsComboboxControlFrame::ReflowComboChildFrame.
This sounds something like bug 263992, which was known to be caused by bug 238493. FWIW.
Yes, I'm well aware. But that bug was Windows-only and caused by a Windows-specific widget issue, whereas this one is happening on Linux (though it would be good to test it on Windows if you could).
(In reply to comment #2) > Yes, I'm well aware. But that bug was Windows-only and caused by a > Windows-specific widget issue, whereas this one is happening on Linux (though it > would be good to test it on Windows if you could). I was seeing something just like that on a Windows build in the same timeframe (I don't have it at hand now, because I went back to an older build and I'm not at that machine right now to try it out again). I assumed it was bug 263992 though, so I never wrote a separate bug report. I'll try to retry it on Windows again when I'm back at that machine.
Attached image Windows screenshot
This is what you get on Windows following the reproduction steps given here. The popup of options winds up in the wrong place, but not I think the same wrong place being described on Linux. This is the 20041013 build, Win2K.
Blocks: 264267
No, that's about what I see on Linux....
Attached file Minimalish testcase
Testcase works for me in the 20041014 build, Win2000.
OK, I hunted down why this happens in the GTK1 impl. Comments on the other impls will follow after the GTK1 problem description. The basic problem is that calling Move(x, y) on a widget whose bounds already have (x, y) as the top-left corner of the rect is NOT a no-op. The code checked in for bug 238493 assumes it is. To be precise, there are two issues, and it was pure accident that this stuff used to work. Bug 64902 was not at all an accident. The first issue is that GTK1 nsWindow impls keep their _screen_ position (Move() uses coordinates relative to the parent widget) cached. Calling nsWindow::Move() invalidates this cache no matter what values are passed in. This is rather backwards, because what Move() should really be doing is invalidating the cache of all _descendants_ for cases when we actually move (since their screen positions have now changed while their bounds have not). This is what caused bug 64902, essentially, as far as I can tell -- the iframe's widget doesn't move relative to parent, so keeps its cached screen position and the popup positions relative to that; see next paragraph. The second issue (and the reason we have this caching) is that popup-type windows (like combobox popups) actually have to position themselves relative to the _screen_. At the moment this is hacked in by not bailing out of nsWindow::Move() for popups (or rather all toplevel windows) even if the passed-in parent-relative values are equal to the existing parent-relative values, "because the parent may have moved". But we're not even calling into Move() now, so this hack has no effect. So really, moving a widget should not only invalidate the caches of all descendants but also reposition all descendant popups appropriately.... In any case, simply setting |changedSize = changedPos = PR_TRUE;| in nsView::ResetWidgetBounds fixes this bug and bug 64902 for GTK1. I don't think that's really what we want to do, though. It'd be better to fix widgets to not make random assumptions about Move() being called on things that are not actually moving (which is what would be needed to fix bug 64902 otherwise). At the same time that could lead to a lot of traversal of the widget tree... Anyway, given all of the above, I looked at all our Move() and Resize() methods, with the following results: 1) BeOS. nsWindow::Move is not a no-op for popups as above, since they position relative to screen. 2) Cocoa. nsCocoaWindow::Move has the same issues for popups. It may have other issues too; I can't tell for sure. 3) GTK1. As described above, and nsWidget::Resize and nsWindow::Resize do some work no matter what (but this may not be necessary; in fact doesn't look like it is). 4) GTK2. nsWindow::Move has the usual popup issues. There is no screen position cache, unlike GTK1, but this code is still assuming that Move() will actually be called on the popup's widget any time an ancestor widget moves. 5) Mac. nsWindow::Resize always recalculates window regions, even if the size didn't change, because "our parents may have changed" (nice comment by pinkerton to that effect). 6) OS/2. Looks like it may be ok, but I don't really grok that code since I don't know the system APIs there. 7) Photon. nsWindow::Move has the usual popup issues. 8) QT. Looks ok to me (assuming that the nsIWidget impl is nsCommonWidget here) 9) Windows. nsWindow::Move has the usual popup issues (needs to be called if parent has moved). nsWindow::Resize (the version that takes x/y values) has the same issues. 10) Xlib. This is basically a copy/paste of GTK1 and has basically the same issues.
> |changedSize = changedPos = PR_TRUE;| How about we just have nsView::ResetWidgetBounds always call nsIWidget::Resize(x,y,w,h) and leave various optimizations to the widget layer? That will fix the bug, isolate the brokeness to individual widget implementations, and allow any performance issues to be fixed there piecemeal.
> That will fix the bug In general, yes, since we're widget caching around reflow and at the end of an update we'd walk the entire widget tree and call Resize() on all of them. But if some code doesn't batch view updates and does something that repositions widgets it could still run afoul of this issue. Now I'm all for not supporting any sort of widget geometry manipulations outside of view update batches (though combobox may be doing that now when it drops down... I can't recall). But if so, we should clearly document that in the relevant apis (nsIWidget, nsIView, nsIViewManager) and remove the code in views that repositions/resizes widgets when the views are repositioned/resized. If we do plan to support both modes of operation, we need to fix the widget impls after all... In any case, no matter what we decide, we need to clearly document it all in nsIWidget.
Alright, let's see what's entailed in fixing widgets. It sounds like the following: 1) When we move a widget, traverse the widgets under it and move any popups we find. 2) When we move a widget, traverse the widgets under it and invalidate any cached screen coordinates they may have. (GTK1 only?) But I think it would be OK for popups to not move while they're visible. If so, then we can replace 1) with 1b) When a popup becomes visible (via Show() or size change away from (0,0)), reposition it at the correct screen offset. 1b) and 2) seem reasonable to implement, IMHO.
> 1b) and 2) seem reasonable to implement, IMHO. Agreed. Again, we should document all this in the nsIWidget apis as needed. Help out authors of new widget impls if nothing else... I'll defer to blizzard and bryner on whether it's faster to walk the kids every time clearing the cache or just not cache at all (the cache is used by WidgetToScreen(), and I don't really know what the performance expectations for that code are).
Note that this still leaves the mac Resize() code, though... I can't tell what that code is trying to do, unfortunately .
I'm not sure we want to say that Move() *never* moves popups, because maybe we will find a need to move popups while they're visible. Probably we should just say that changes in the geometry of a popup's parent widget chain will not take effect while the popup is visible.
But that means that Move(x,y) ... Move(x,y) might not be idempotent if there is a parent geometry change in the middle ... argh.
argh indeed....
Then what I suggest is the following policy: Move() and Resize(x,y,w,h) do not move popups immediately. Popups only change position to the desired position when they go from hidden to shown. If later we discover a need to move visible popups, we can add a RepositionPopup method to nsIWidget which will move the popup to the desired position.
OK. So from the widget impl point of view to get correct functioning: 1) Move/Resize will be called on things in some order not to be depended on. 2) Popups should be properly positioned in the Show() call. 3) Move/Resize on the popup itself have no effect while it's shown. We leave the viewmanager code as-is and file followup bugs on widget impls that try to compensate for changes to parents when Move/Resize is called on a kid. Widget owners, does that seem reasonable?
Actually Show(PR_TRUE) can force the popup to be repositioned even if it's already visible, so no need for a new RepositionPopup API. > 3) Move/Resize on the popup itself have no effect while it's shown. Actually it's simpler to say "Move/Resize on popups does not take effect until Show(PR_TRUE) is called". If the popup is not currently shown then there is no visible effect anyway until it becomes shown.
> 4) GTK2. nsWindow::Move has the usual popup issues. There is no screen > position cache, unlike GTK1, but this code is still assuming that Move() will > actually be called on the popup's widget any time an ancestor widget moves. Careful. Gtk2 actually caches this internally. That's why it's not implemented in our widget code.
> > We leave the viewmanager code as-is and file followup bugs on widget impls that > try to compensate for changes to parents when Move/Resize is called on a kid. So you're assuming a very strong connection here between popups and their parents? I don't think that's ever existed before and it makes my spider sense tingle. We want to avoid doing any _actual_ movement of widgets until they are actually shown. This is a huge cost in X, because of all of the server round trips that are required when you position relative to a parent or screen. Also, why is it painful to do this at a higher level in one place instead of doing it in several different widget implemenations? That's not clear to me.
> Actually it's simpler to say "Move/Resize on popups does not take effect until > Show(PR_TRUE) is called". If the popup is not currently shown then there is no > visible effect anyway until it becomes shown. Sorry, I didn't read this before replying. I'm pretty sure that the gtk2 code does this. It took a long time to get right.
(In reply to comment #20) > Careful. Gtk2 actually caches this internally. So if I have two widgets, widget1 child of widget2. And I call Move() on widget2 (which changes the screen positions of both widgets, but not the nsIWidget position of widget1). Then the cache of widget1 will _not_ be updated? If it's properly updated then we don't care that there is a cache. The problem is that GTK1 doesn't update the cache, not that it has one.
> Also, why is it painful to do this at a higher level in one place instead of > doing it in several different widget implemenations? That's not clear to me. If you can come up with a simple specification for how popup positioning works, which explains in detail when callers can and must call Move(), Resize(x,y,w,h), etc, that is true for all of our widget implementations, and isn't going to cost us performance now or looking forward, then we could do it at the higher level. Boris and I don't see it.
A problem with the approach in comment #17 is that it raises the question "what happens if you do GetBounds or WidgetToScreen on a popup after calling Move() but before calling Show(PR_TRUE)"? It also may complicate things in general to allow an inconsistency between the "specified" position and the "actual" position. Right now I'm leaning towards specifying simply that Move() or Resize(x,y,w,h) on a widget *must* put all descendants in the right place, popups or not, as seen on screen and as observed by GetBounds, WidgetToScreen, etc. That's the simplest policy. Widget implementations can optimize the movement of hidden popups subject as long as they produce the correct observable behaviour.
If we do that, is there any way we can prevent having to do extra work on the widget tree? Possibly walk the view tree twice, first calling WillMove() (or something) on all views we plan to move, then calling Move(). This way widgets can mark subtrees as "will be moved, don't have to deal with it now". Or is that case (of having to call Move() on several widgets that are kids of each other) rare enough that optimizing it is not realy worth it?
In what situations will we have a lot of widgets? I think only when you have a page with a lot of scrolling elements (overflow:auto, overflow:scroll, listboxes, comboboxes, edit boxes). My guess is that even in these cases the widget tree will be fairly shallow. And if you ensure that you only scan the widget subtree when the root of that subtree is actually moving, then I think we will find in practice that non-leaf widgets don't move very often, so we don't scan a big widget subtree very often. And scanning itself should be quite cheap, it's only when we have lots of popups that must be moved that we have a problem. On GTK1/2 we're going to only have to actually move visible popups, which won't be many, and on Windows we can do that too, or we can just move all of them if that's cheap. Dunno about Mac. An alternative to doing all this might be to change our model for popups so that the popup widget simply has no parent, which is much more in line with what the underlying window systems do. I don't know what the implications of that might be...
Agreed about the fact that non-leaf widgets don't move much. Sounds like we have a plan, then.
Actually at the moment I sorta favour my last idea.
I'm not really wise enough in the ways of widgetry to evaluate that one... blizzard? Others?
Attached patch fix? (obsolete) — Splinter Review
This patch seems to work and fix things on GTK1. It doesn't seem to need any widget implementation changes at all. (But once we verify that all popup widgets have no parent, we can probably simplify some crufty code in a lot of places.) It *may* cause more widget motion than we currently do, but I doubt it matters. Anyway it would be relatively easy to fix GTK1/2 to delay moving hidden popups.
This fixes the other problem bz noted in GTK1: invalidate the position cache on the entire widget subtree when the root of the subtree moves. Also, I noticed that GetWindowPos only needs to go to the X server for root widgets and can use the cached window pos on the root widget otherwise.
Attached patch fix v2Splinter Review
I encountered some problems with GTK2. To fix them, I made popups be roots in the nsIWidget tree but still be parented in the native widget tree.
Attachment #162372 - Attachment is obsolete: true
fix v2 stilll works great in GTK1. Do I have any volunteers to test it in Windows and Mac?
(In reply to comment #27) > In what situations will we have a lot of widgets? Remember that we also have very complicated xul dialogs, like ff's prefs dialog. that have lots of scrolly bits and complex UI. We're not solely talking web pages here.
I think the sort of page which has given us trouble in the past is the Slashdot moderation page, where you have a complicated layout containing several hundred comboboxes. That should be alright with these changes, I hope.
(In reply to comment #34) > fix v2 stilll works great in GTK1. Do I have any volunteers to test it in > Windows and Mac? I've tested this fix v2 on Windows with my mingw debug build of Mozilla. The bug mentioned here is not visible with or without the patch in Windows. But I think I see some regressions with this fix: http://home.hccnet.nl/m.wargers/test/mozilla/menu.png The menu popup gets too low. http://home.hccnet.nl/m.wargers/test/mozilla/menu_offsetx.png http://home.hccnet.nl/m.wargers/test/mozilla/menu_offsety.png It seems as if the amount of distance it is too left or too right is the same amount as the x,y position of the Mozilla window on the screen. Also in the preference menu the drop down menus get too low: http://home.hccnet.nl/m.wargers/test/mozilla/preferences.png
Attached patch Windows fix?Splinter Review
Martjin, if you apply this patch, does that make your Windows problems go away?
Yep, that seems to fix it all :)
Attached patch combined fixSplinter Review
This is all the fixes rolled together, plus some assertions to catch anyone passing in a popup with an nsIWidget parent (which is forbidden now).
Oops. I still need to get this working on Mac, apparently. I've attached a proposed extra fix for the Mac in bug 264235, attachment 162697 [details] [diff] [review]
I guess we should add a comment in nsIWidget.h, like this.
Comment on attachment 162696 [details] [diff] [review] combined fix I'd like to check this in ASAP. I know it will unbust GTK/GTK2, and should at least get Windows through smoketests although there could still be regressions left over as described in bug 263992 comments. It doesn't fix Mac yet, apparently, but it doesn't bust Mac any more than it's already busted.
Attachment #162696 - Flags: superreview?(blizzard)
Attachment #162696 - Flags: review?(bzbarsky)
Comment on attachment 162696 [details] [diff] [review] combined fix r=bzbarsky
Attachment #162696 - Flags: review?(bzbarsky) → review+
Comment on attachment 162702 [details] [diff] [review] change to comments r+sr=bzbarsky
Attachment #162702 - Flags: superreview+
Attachment #162702 - Flags: review+
Attachment #162696 - Flags: superreview?(blizzard) → superreview+
I checked in these patches. As far as I know, only Mac still has problems. See bug 264235.
Status: NEW → RESOLVED
Closed: 21 years ago
Resolution: --- → FIXED
Robert, those fix bug 64902 as well, right?
I hope so, but I haven't checked.
I don't see bug 64902 fixed in my GTK1 that I built 12 hours ago. Also, whenever a popup opens its "parent" deactivates. Related?
> Also, whenever a popup opens its "parent" deactivates. Related? Probably. GTk1 probably needs to relate the popup to its native widget parent in a way similar to what GTK2 does. Care to fix it? :-)
Another bug I see in GTK1 but not on Windows - the open location dialog opens at a different location (!) on the screen every time...
Depends on: 265841
Component: Layout: View Rendering → Layout: Web Painting
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: