Skip to content
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

[url_launcher][web] Fix Link issues when Semantics are enabled #6579

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

htoor3
Copy link

@htoor3 htoor3 commented Apr 19, 2024

DRAFT PR - will add tests shortly.

This PR fixes an issue where Links break in Semantics mode (clicking links does nothing) because the Link's child semantics obscure the platform view.

The fix:

  • Clean up the extraneous semantics nodes being generated by PlatformViewLink so we can consolidate all of the link information in a single place. Also exclude it from the focus tree to fix the "double tabbing" issue.
  • Pass the link role and URI to the child semantics so that we can wrap the node in an <a> tag to enable the context menu and provide link preview info. This requires a separate engine PR.
  • Click events never bubbled back up when clicking the Link's child semantics nodes (due to stopPropagation being called in the engine), so we have to handle click events in the capture phase.
  • Handle link trigger logic during hit test registration (not just during browser events).

For keydown events, things remain the same:

  1. User hits enter
  2. Framework receives + handles event, executes followLink, registers a hit test view id
  3. Browser receives event and triggers link behavior using the registered viewId
  4. Cleanup

For click events, things are slightly different depending on if the node that's clicked is a semantic node or a platform view link.

Clicking platform view links will behave similarly to keydown events.

Clicking the generated semantic node of a link (specifically its children) will behave as follows:

  1. User clicks button
  2. Browser receives + handles event. No hit test id is registered yet, so we just cache the click event for later
  3. Framework executes followLink, registers a hit test view id. Triggers link behavior using the registered viewId
  4. Cleanup

Fixes flutter/flutter#143164

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use dart format.)
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].
  • I updated CHANGELOG.md to add a description of the change, [following repository CHANGELOG style].
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

static bool followLinkTriggered = false;

/// Keeps track of the last mouse event captured from our global click handler.
static html.MouseEvent? triggeredMouseEvent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:

Suggested change
static html.MouseEvent? triggeredMouseEvent;
static html.MouseEvent? lastMouseEvent;

Comment on lines 239 to 248
triggeredMouseEvent = event;

if (followLinkTriggered) {
final int? viewId = getViewIdFromTarget(event) ?? _hitTestedViewId;

_instances[viewId]?._triggerLink(event: event, isKeyboardEvent: false);
// After the DOM click event has been received, clean up the hit test state
// so we can start fresh on the next event.
unregisterHitTest();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to do the same thing for keyboard events too.

@@ -222,11 +262,26 @@ class LinkViewController extends PlatformViewController {
/// `click` from the browser.
static void registerHitTest(LinkViewController controller) {
_hitTestedViewId = controller.viewId;
followLinkTriggered = true;

if (triggeredMouseEvent != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do the same for keyboard events (we probably don't need a triggeredKeyboardEvent, but we need a boolean at least).

Here's how I would do it, but feel free to adjust/modify it to how you see fit:

// Set to true in registerHitTest()
static bool followLinkTriggered = false;

// Set to true in _onGlobalClick() and _onGlobalKeydown()
static bool domEventTriggered = false;

// Set only in _onGlobalClick()
static html.Event? triggeredEvent;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my testing, the keyboard event path doesn't deviate from hit test -> global keydown. Do you think this boolean is necessary because there would be a case where the browser keydown handler executes before the framework handles it?

I had to be pretty careful about this in the click handler because we were running into issues where the dom event trigger was being set to true for arbitrary clicks (e.g. clicking elsewhere on the app). This led to issues where we would trigger the link twice if the event path went hit test -> global click

// We only want to cache the mouse event if its target is a link, so that
// we can later prevent its default behavior and handle how it should
// open the link ourselves.
if(isLinkElement(targetElement) || isSemanticLinkElement(targetElement)) {
Copy link
Author

@htoor3 htoor3 Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdebbar I had to add this to fix the issue of caching mouse events that aren't relevant to us (since the global handler captures every mouse event).

I added isSemanticLinkElement which just checks if the target element is a semantic node that's wrapped within an <a> tag. Is this robust enough to handle the semantic link case? Or should we find a way to associate a platform view's viewId to the generated semantic nodes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants