-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Popover parent/child relationship doesn't seem correct #9160
Comments
cc @mfreed7 |
I believe maintaining a tree of popovers is to handle the case of opening or closing nested popovers should close non-ancestral auto popovers, but the tree of popovers from which the descendent lies should not be closed. |
Can you give an example? |
This was discussed just now at OpenUI. Notes: https://www.w3.org/2023/04/13-openui-minutes.html I'll summarize the discussion later today. |
Ok, so we had a very good discussion of this issue at OpenUI - see meeting minutes above. There were a number of issues raised, but at its core, there are essentially two possible behaviors we could choose from:
A number of variations of #2 were proposed, such as adding an option to While we didn't reach any firm conclusions, it seemed (to me at least) like the tone of the conversation was trending toward supporting #2. In particular, these use cases were discussed:
I believe option #2 would simplify the spec and implementation, because we wouldn't have to track all invokers on the page, but only the last used invoker (see related issue #9152). I will start thinking about these changes along with @josepharhar, but if there are objections to this change, it'd be good to know what they are. @annevk @nt1m |
Side note: I realized why it seems that the Chromium implementation differs from the spec, for the example in the OP. When the non-popover-container invoker for popover-2 is clicked, that is a light-dismiss signal for popover-1, since it is a) outside popover-1 and b) isn't a click on an invoker for popover-1. So when you click on that button:
I don't think this changes the logic or new direction set in the comment above, but it does likely mean it isn't a material change in behavior that users will see. |
lol yeah it's obvious now you say it. Annoyed that I didn't realise that. The difference in behaviour can be seen if you interact with the demo via keyboard, which doesn't trigger light-dismiss. In fact, that seems like a good argument for option 2, as it avoids creating a difference between mouse and keyboard interactions.
Although we'll have to track the invoker of a popover for focus reasons, I don't think we should use that when deciding which popover 'owns' another. When a button is clicked to show a popover, we should look to see if that button is within an auto-popover, and make that the 'owner' of the new popover. From that point, the button no longer matters. Eg, if the button is (re)moved, it doesn't change or break the popover relationships. It feels like the relationship between popovers becomes a simple list: popover1 -> popover2 -> popover3 -> popover4 Where the previous item in the list 'owns' the next item. So, if a new popover is opened that would be owned by popover2, we know we need to close popover3 and popover4. |
If this involves looking at the steps for closing related popovers, it might be worth addressing #9161 at the same time. |
The interactions between popovers and user actions is more complicated than it seems on the surface. I came to this realization a while ago while working on popover. I think this is actually the biggest utility provided by the API - untangling this behavioral stuff and implementing it once, correctly, rather than making everyone re-invent this wheel.
Well, yes, this was kind of why I built the algorithm to not depend on behavior. However, from the meeting, it seemed like people were starting to prefer a "path dependent" algorithm that depends on what the user does. This is another such path dependency - did the user use the keyboard or the mouse? Moving focus with the keyboard doesn't trigger light dismiss (and yes we litigated that extensively) but clicking the mouse does. That's the expected typical behavior - users mostly don't expect moving focus to affect the page state, while they do expect clicking to affect state.
This is close to what's there now, but different in nuanced and important ways. The list above is essentially the auto popover list. The reason you can't just handle this from button clicks, as you suggest above, is that there are other ways to link popovers. For example: <div popover id=p1>p1
<div popover id=p2>p2</div>
<div popover id=p3>p3</div>
</div>
<script>
p1.showPopover();
p2.showPopover();
p3.showPopover(); // Hides p2, leaves p1 open
</script> I really think using the |
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b
Fixes whatwg#9160 If this patch is merged, then whatwg#9048 is obsolete and can be closed without merging. Rationale (copied from [masons patch](https://chromium-review.googlesource.com/c/chromium/src/+/4429412)): Instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this change, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this patch simplifies the implementation, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed.
fwiw, I wasn't disagreeing with this.
Totally agree. I just didn't mention that stuff as it doesn't seem like there's any disagreement there, but yeah, that wasn't clear.
We might be talking about the same thing, but I really think the invoker should be resolved to a parent popover at 'show' time, rather than 'hide' time (the current spec looks at buttons at 'hide' time). That means, if the invoker button is (re)moved after it's clicked, it doesn't change the relationship of the popover to its parent.
We'd need to figure out what: el.showPopover({ invoker: foo });
el.showPopover({ invoker: bar }); …does. |
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b
I think we agree, and I think that's what the new PR actually does. The invoker is checked/used at the time that it's target is shown, i.e. when the invoker is clicked. Since the invoker is there, it allows the popover containing that invoker to stay open while the target popover is opened. After that point, both popovers are now open and in the popover stack, and the invoker shouldn't matter for the hierarchy any longer.
I suggested |
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b Cq-Do-Not-Cancel-Tryjobs: true
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4429412 Commit-Queue: Mason Freed <masonf@chromium.org> Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Joey Arhar <jarhar@chromium.org> Cr-Commit-Position: refs/heads/main@{#1131606}
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4429412 Commit-Queue: Mason Freed <masonf@chromium.org> Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Joey Arhar <jarhar@chromium.org> Cr-Commit-Position: refs/heads/main@{#1131606}
See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4429412 Commit-Queue: Mason Freed <masonf@chromium.org> Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Joey Arhar <jarhar@chromium.org> Cr-Commit-Position: refs/heads/main@{#1131606}
…opover hierarchy, a=testonly Automatic update from web-platform-tests Only use the used invoker to establish popover hierarchy See [1] for more context, but the idea is that instead of using just the DOM structure to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this CL, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this CL simplifies the implementation considerably, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Spec PR: whatwg/html#9171 [1] whatwg/html#9160 Bug: 1307772 Change-Id: I60ccb133a79084db8c251218fdbd10684fea947b Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4429412 Commit-Queue: Mason Freed <masonf@chromium.org> Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Joey Arhar <jarhar@chromium.org> Cr-Commit-Position: refs/heads/main@{#1131606} -- wpt-commits: 5acfa513ebd07b44adc8be2fc2c491e1b90236da wpt-pr: 39560
Rationale from Mason Freed as per https://chromium-review.googlesource.com/c/chromium/src/+/4429412: Instead of using the node tree to establish the popover hierarchy, the user's behavior should matter. For example, if one popover contains a popover invoker pointing to another popover, it should matter whether that invoker is *actually used* to open the second popover. An example: - Component 1 is a third party widget, which uses popover - Component 2 is another third party widget, also using popover - A page wants to use both components separately, from separate invoking buttons. - Component 1 also wants to be able to use Component 2, via a button within Component 1. In this example, the page should be able to still independently use these components. So a user clicking the page's button for Component 2 is expected to close Component 1 if it's open, because that's a direct invocation of Component 2. However, if the user clicks the button within Component 1 to get Component 2, it is natural to leave Component 1 open because this is a nested call. Important note: this often happens to be the behavior even before this change, since the user clicking on the page-level Component 2 invoking button represents a light dismiss signal for Component 1, so it closes either way. But this simplifies the logic, removing the need to track all invokers on the page, and also removing the need to continuously check whether invoker relationships have changed. Fixes #9160, fixes #9168, and closes #9048 (as per discussion in the PR).
https://bugs.webkit.org/show_bug.cgi?id=255878 Reviewed by Tim Nguyen. Implement this behaviour after the discussion here: whatwg/html#9160 * LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-light-dismiss-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-light-dismiss.html: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-shadow-dom.html: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-target-element-disabled-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/html/semantics/popovers/popover-target-element-disabled.html: * LayoutTests/platform/glib/imported/w3c/web-platform-tests/html/semantics/popovers/popover-light-dismiss-expected.txt: * LayoutTests/platform/ios/imported/w3c/web-platform-tests/html/semantics/popovers/popover-light-dismiss-expected.txt: * Source/WebCore/html/HTMLElement.cpp: (WebCore::topmostPopoverAncestor): (WebCore::HTMLElement::checkAndPossiblyClosePopoverStackInternal): Deleted. * Source/WebCore/html/HTMLElement.h: (WebCore::HTMLElement::checkAndPossiblyClosePopoverStack): Deleted. * Source/WebCore/html/HTMLFormControlElement.cpp: (WebCore::HTMLFormControlElement::removedFromAncestor): (WebCore::HTMLFormControlElement::attributeChanged): (WebCore::HTMLFormControlElement::disabledStateChanged): (WebCore::HTMLFormControlElement::didChangeForm): Deleted. * Source/WebCore/html/HTMLFormControlElement.h: * Source/WebCore/html/HTMLInputElement.cpp: (WebCore::HTMLInputElement::updateType): Canonical link: https://commits.webkit.org/264002@main
The algorithm is really complex, so I might be reading it incorrectly.
Consider markup like this:
Demo
When the spec is deciding which popovers to close, it looks at all buttons on the page rather than the button that was used. This doesn't make sense to me, and doesn't match Chrome's implementation.
https://youtu.be/VO1C5YPITsk - here's a readthrough of the spec.
I'm not sure what the actual intended behaviour is (the spec is complicated and buggy). The note mentions a "tree of popovers", but it seems like only a list is needed? Is there a case where two open popovers have the same parent?
I also feel we should match buttons up to closest popovers early. The current spec looks at the position and state of the button when a new popover is opening, but that feels like the wrong time to check. Eg, a button must be enabled when it's used to open a popup. It doesn't matter if it's disabled when later trying to determine the popover tree. In fact, the button may have been later removed.
The text was updated successfully, but these errors were encountered: