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] Better support for semantics in the Link widget #6711

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

Conversation

mdebbar
Copy link
Contributor

@mdebbar mdebbar commented May 10, 2024

  • Better support for semantics.
  • Better support for clicks with a modifier key (e.g. cmd+click).
  • More robust handling of events (can now correctly handle events received in a different order).
  • Apply the target attribute on semantic links.
  • Improve tests.

Fixes flutter/flutter#143164
Fixes flutter/flutter#146291

@@ -42,13 +47,42 @@ class WebLinkDelegate extends StatefulWidget {
WebLinkDelegateState createState() => WebLinkDelegateState();
}

extension on Uri {
String getHref() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to make sure first that the code is starting to look good before I spend time on writing and re-writing docs.

packages/url_launcher/url_launcher_web/lib/src/link.dart Outdated Show resolved Hide resolved
packages/url_launcher/url_launcher_web/lib/src/link.dart Outdated Show resolved Hide resolved
// Why listen in the capture phase?
//
// To ensure we always receive the event even if the engine calls
// `stopPropagation`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would the engine stop propagating the events? Seems like code smell in the engine, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason, it already does here.

The problem is a combination of two things:

  1. The engine is listening to keyboard events in the capture phase.
  2. The engine is calling stopPropagation on the event when the framework handles it.

I wanted to make the Link widget robust in all kinds of scenarios, at least until we fix the above 2 issues.

/// Keeps track of the signals required to trigger a link.
///
/// Automatically resets the signals after a certain delay. This is to prevent
/// the signals from getting stale.
Copy link
Contributor

Choose a reason for hiding this comment

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

This description is very generic, even though the class does something very specific. AFAICT this class is resolving some race where the viewId and the mouse event can come in at unpredictable times and in unpredictable order. The dart docs could talk about the specifics, and also explain how this race arises.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I'll revisit this when I do the pass to fill in missing docs.

_hitTestedViewId = null;
if (_triggerSignals.isReadyToTrigger) {
_triggerLink();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The pattern of this if block looks the same everywhere. Can we pass the _triggerLink function to LinkTriggerSignals and have it call it at the appropriate time automatically? It already has the knowledge of when it is appropriate to call it. LinkTriggerSignals will no longer need isReadyToTrigger (except maybe for testing).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are cases where I also check for isValid first and do something else. But I think that also can be moved to the LinkTriggerSignals class. I like the idea so I'll take a stab at it.

// We only want to handle clicks that land on *our* links, whether that's a
// platform view link or a semantics link.
final int? viewIdFromTarget = _getViewIdFromLink(target) ??
_getViewIdFromSemanticLink(target);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this work if <flutter-view> is inside shadow DOM? In embedded case in particular, we don't know how exactly a FlutterView is embedded. It could be inside a web component's shadow DOM. In that case event.target caught at window level will simply point to the web component. It won't tell you the exact element the mouse event happened on.

Another issue is that this relies on the semantic DOM internals, so it can easily break if the structure changes.

Can we use https://api.flutter.dev/flutter/semantics/SemanticsProperties/onTap.html instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding <flutter-view> being inside shadow DOM, that's a very good point that I hadn't considered. I'll test it and fix it.

Regarding relying on the semantic DOM internals, I couldn't find a better way to implement it. Using onTap doesn't work because the children of the Link widget typically have their own onTap which absorbs the tap event so it never reaches the Link's onTap.

auto-submit bot pushed a commit to flutter/engine that referenced this pull request Jun 20, 2024
This is a stopgap for issues like flutter/flutter#146291 until we land better link semantics (in flutter/flutter#150263 and flutter/packages#6711). For the time being, the `href="#"` isn't providing any extra value, and is causing the browser to navigate to to `#` whenever the semantics link is clicked, which is undesirable.

Fixes flutter/flutter#146291
auto-submit bot pushed a commit to flutter/engine that referenced this pull request Jun 20, 2024
…3278)

Make [`Semantics(identifier: '...')`](https://api.flutter.dev/flutter/semantics/SemanticsProperties/identifier.html) useful on the web. This PR plugs the Semantics `identifier` property as an HTML attribute `semantics-identifier` onto semantics elements.

This is useful in some scenarios:
- In testing to check if a certain semantics node has made it to the page ([example](flutter/flutter#97455)).
- In apps and/or packages to be able to lookup the DOM element that corresponds to a certain semantics node ([example](flutter/packages#6711)).

Fixes flutter/flutter#97455
auto-submit bot pushed a commit to flutter/engine that referenced this pull request Jul 3, 2024
The new property allows the user to specify a URI for their semantics link node. It's plumbed through for both web and non-web engines, but it's only used in the web engine currently. It sets the `href` of the anchor element associated with semantics node.

This is going to unlock better semantics support in the Link widget on web ([PR](flutter/packages#6711)).

Framework counterpart: flutter/flutter#150639

Part of flutter/flutter#150263
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Jul 9, 2024
The new property allows the user to specify a URI for their semantics link node. On the web, it sets the `href` of the anchor element associated with semantics node.

This is going to unlock better semantics support in the Link widget on web ([PR](flutter/packages#6711)).

Engine counterpart: flutter/engine#53507

Fixes #150263
@kevmoo kevmoo closed this Jul 16, 2024
@harryterkelsen
Copy link
Contributor

Re-opening this. It needs to land for flutter/flutter#143164 to be fixed

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