Skip to content

Commit

Permalink
[pointer_interceptor_web] Update package APIs and tests. (flutter#5785)
Browse files Browse the repository at this point in the history
## Changes:

* Updates the web implementation of the `pointer_interceptor` to `package:web`, and the latest `HtmlElementView` APIs available on Flutter.
* Resolves a timing issue in integration tests by waiting a few frames before doing any assertions from the DOM of a Flutter Web app, that should fix flutter rolls.

## Issues:

* Fixes flutter/flutter#140834
* Fixes flutter/flutter#139753
* Closes flutter#5673 (Credit to @balvinderz!)

## Tests

* Tested manually. All integration tests pass locally.

---

Co-authored-by: balvinderz <balvindersi2@gmail.com>
  • Loading branch information
2 people authored and arc-yong committed Jun 14, 2024
1 parent 187df9d commit 1c2f81e
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 148 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## 0.10.1

* Uses `HtmlElementView.fromTagName` instead of custom factories.
* Migrates package and tests to `platform:web`.
* Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.0.

## 0.10.0

* Moves web implementation to its own package.

Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@

// ignore_for_file: avoid_print

import 'dart:html' as html;

// Imports the Flutter Driver API.
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'package:pointer_interceptor_web_example/main.dart' as app;
import 'package:web/web.dart' as web;

final Finder nonClickableButtonFinder =
find.byKey(const Key('transparent-button'));
final Finder clickableWrappedButtonFinder =
find.byKey(const Key('wrapped-transparent-button'));
final Finder clickableButtonFinder = find.byKey(const Key('clickable-button'));
final Finder backgroundFinder =
find.byKey(const ValueKey<String>('background-widget'));
final Finder backgroundFinder = find.byKey(const Key('background-widget'));

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
Expand All @@ -28,10 +25,9 @@ void main() {
testWidgets(
'on wrapped elements, the browser does not hit the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(clickableButtonFinder, tester);

expect(element.id, isNot('background-html-view'));
Expand All @@ -40,10 +36,9 @@ void main() {
testWidgets(
'on wrapped elements with intercepting set to false, the browser hits the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);

expect(element.id, 'background-html-view');
Expand All @@ -52,20 +47,18 @@ void main() {
testWidgets(
'on unwrapped elements, the browser hits the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);

expect(element.id, 'background-html-view');
}, semanticsEnabled: false);

testWidgets('on background directly', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));

expect(element.id, 'background-html-view');
Expand All @@ -75,15 +68,9 @@ void main() {
group('With semantics', () {
testWidgets('finds semantics of wrapped widgets',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

if (!_newSemanticsAvailable()) {
print('Skipping test: Needs flutter > 2.10');
return;
}

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(clickableButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
Expand All @@ -93,15 +80,9 @@ void main() {
testWidgets(
'finds semantics of wrapped widgets with intercepting set to false',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

if (!_newSemanticsAvailable()) {
print('Skipping test: Needs flutter > 2.10');
return;
}
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
Expand All @@ -111,15 +92,9 @@ void main() {

testWidgets('finds semantics of unwrapped elements',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

if (!_newSemanticsAvailable()) {
print('Skipping test: Needs flutter > 2.10');
return;
}

final html.Element element =
final web.Element element =
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
Expand All @@ -134,20 +109,26 @@ void main() {
// simply allows the hit test to land on the platform view by making itself
// hit test transparent.
testWidgets('on background directly', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
await _fullyRenderApp(tester);

final html.Element element =
final web.Element element =
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));

expect(element.id, 'background-html-view');
});
});
}

Future<void> _fullyRenderApp(WidgetTester tester) async {
await tester.pumpWidget(const app.MyApp());
// Pump 2 frames so the framework injects the platform view into the DOM.
await tester.pump();
await tester.pump();
}

// Calls [_getHtmlElementAt] passing it the center of the widget identified by
// the `finder`.
html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) {
web.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) {
final Offset point = tester.getCenter(finder);
return _getHtmlElementAt(point);
}
Expand All @@ -158,22 +139,20 @@ html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) {
// sensitive to the presence of shadow roots and browser quirks (not all
// browsers agree on what it should return in all situations). Since this test
// runs only in Chromium, it relies on Chromium's behavior.
html.Element _getHtmlElementAt(Offset point) {
web.Element _getHtmlElementAt(Offset point) {
// Probe at the shadow so the browser reports semantics nodes in addition to
// platform view elements. If probed from `html.document` the browser hides
// the contents of <flt-glass-name> as an implementation detail.
final html.ShadowRoot glassPaneShadow =
html.document.querySelector('flt-glass-pane')!.shadowRoot!;
return glassPaneShadow.elementFromPoint(point.dx.toInt(), point.dy.toInt())!;
final web.ShadowRoot glassPaneShadow =
web.document.querySelector('flt-glass-pane')!.shadowRoot!;
// Use `round` below to ensure clicks always fall *inside* the located
// element, rather than truncating the decimals.
// Truncating decimals makes some tests fail when a centered element (in high
// DPI) is not exactly aligned to the pixel grid (because the browser *rounds*)
return glassPaneShadow.elementFromPoint(point.dx.round(), point.dy.round());
}

// TODO(dit): Remove this after flutter master (2.13) lands into stable.
// This detects that we can do new semantics assertions by looking at the 'id'
// attribute on flt-semantics elements (it is now set in 2.13 and up).
bool _newSemanticsAvailable() {
final html.ShadowRoot glassPaneShadow =
html.document.querySelector('flt-glass-pane')!.shadowRoot!;
final List<html.Element> elements =
glassPaneShadow.querySelectorAll('flt-semantics[id]');
return elements.isNotEmpty;
/// Shady API: https://github.com/w3c/csswg-drafts/issues/556
extension ElementFromPointInShadowRoot on web.ShadowRoot {
external web.Element elementFromPoint(int x, int y);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,51 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:ui_web' as ui_web;

import 'package:flutter/material.dart';
import 'package:pointer_interceptor_platform_interface/pointer_interceptor_platform_interface.dart';
import 'package:pointer_interceptor_web/pointer_interceptor_web.dart';
import 'package:web/web.dart' as web;

const String _htmlElementViewType = '_htmlElementViewType';
const double _videoWidth = 640;
const double _videoHeight = 480;
const double _containerWidth = 640;
const double _containerHeight = 480;

/// The html.Element that will be rendered underneath the flutter UI.
html.Element htmlElement = html.DivElement()
..style.width = '100%'
..style.height = '100%'
..style.backgroundColor = '#fabada'
..style.cursor = 'auto'
..id = 'background-html-view';
final web.Element _htmlElement =
(web.document.createElement('div') as web.HTMLDivElement)
..style.width = '100%'
..style.height = '100%'
..style.backgroundColor = '#fabada'
..style.cursor = 'auto'
..id = 'background-html-view';

// See other examples commented out below...

// html.Element htmlElement = html.VideoElement()
// ..style.width = '100%'
// ..style.height = '100%'
// ..style.cursor = 'auto'
// ..style.backgroundColor = 'black'
// ..id = 'background-html-view'
// ..src = 'https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4'
// ..poster = 'https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217'
// ..controls = true;

// html.Element htmlElement = html.IFrameElement()
// final web.Element _htmlElement =
// (web.document.createElement('video') as web.HTMLVideoElement)
// ..style.width = '100%'
// ..style.height = '100%'
// ..style.cursor = 'auto'
// ..style.backgroundColor = 'black'
// ..id = 'background-html-view'
// ..src =
// 'https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4'
// ..poster =
// 'https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217'
// ..controls = true;

// final web.Element _htmlElement =
// (web.document.createElement('video') as web.HTMLIFrameElement)
// ..width = '100%'
// ..height = '100%'
// ..id = 'background-html-view'
// ..src = 'https://www.youtube.com/embed/IyFZznAk69U'
// ..style.border = 'none';

void main() {
ui_web.platformViewRegistry.registerViewFactory(
_htmlElementViewType,
(int viewId) => htmlElement,
);
runApp(const MyApp());
}

Expand Down Expand Up @@ -81,6 +82,15 @@ class _MyHomePageState extends State<MyHomePage> {
});
}

@override
void initState() {
super.initState();
ui_web.platformViewRegistry.registerViewFactory(
_htmlElementViewType,
(int viewId) => _htmlElement,
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down Expand Up @@ -109,13 +119,13 @@ class _MyHomePageState extends State<MyHomePage> {
),
Container(
color: Colors.black,
width: _videoWidth,
height: _videoHeight,
width: _containerWidth,
height: _containerHeight,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
HtmlElement(
key: const ValueKey<String>('background-widget'),
key: const Key('background-widget'),
onClick: () {
_clickedOn('html-element');
},
Expand Down Expand Up @@ -207,9 +217,12 @@ class HtmlElement extends StatelessWidget {

@override
Widget build(BuildContext context) {
htmlElement.onClick.listen((_) {
onClick();
});
_htmlElement.addEventListener(
'click',
(JSAny? _) {
onClick();
}.toJS,
);

return const HtmlElementView(
viewType: _htmlElementViewType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: "Demonstrates how to use the pointer_interceptor_web plugin."
publish_to: 'none'

environment:
sdk: ">=3.1.0 <4.0.0"
flutter: ">=3.13.0"
sdk: ^3.2.0
flutter: '>=3.16.0'

dependencies:
cupertino_icons: ^1.0.2
Expand All @@ -13,6 +13,7 @@ dependencies:
pointer_interceptor_platform_interface: ^0.10.0
pointer_interceptor_web:
path: ../../pointer_interceptor_web
web: '>=0.3.0 <0.5.0'

dev_dependencies:
flutter_test:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
Fore more details:
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="/">
<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
Expand All @@ -31,18 +34,21 @@

<title>example</title>
<link rel="manifest" href="manifest.json">

<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function(engineInitializer) {
let appRunner = await engineInitializer.initializeEngine();
appRunner.runApp();
}
});
}
});
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
Loading

0 comments on commit 1c2f81e

Please sign in to comment.