-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[pointer_interceptor] Update app-facing package to accommodate new federated structure #5501
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
## 0.10.0 | ||
|
||
* Adds iOS implementation. | ||
|
||
## 0.9.3+7 | ||
|
||
* Updates metadata to point to new source folder | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,24 +1,25 @@ | ||||||
# pointer_interceptor | ||||||
|
||||||
`PointerInterceptor` is a widget that prevents mouse events (in web) from being captured by an underlying [`HtmlElementView`](https://api.flutter.dev/flutter/widgets/HtmlElementView-class.html). | ||||||
| | iOS | Web | | ||||||
|-------------|---------|-----| | ||||||
| **Support** | iOS 11+ | Any | | ||||||
|
||||||
You can use this widget in a cross-platform app freely. In mobile, where the issue that this plugin fixes does not exist, the widget acts as a pass-through of its `children`, without adding anything to the render tree. | ||||||
`PointerInterceptor` is a widget that prevents mouse events from being captured by an underlying [`HtmlElementView`](https://api.flutter.dev/flutter/widgets/HtmlElementView-class.html) in web, or an underlying [`PlatformView`](https://api.flutter.dev/flutter/widgets/PlatformViewLink-class.html) on iOS. | ||||||
|
||||||
## What is the problem? | ||||||
|
||||||
When overlaying Flutter widgets on top of `HtmlElementView` widgets that respond to mouse gestures (handle clicks, for example), the clicks will be consumed by the `HtmlElementView`, and not relayed to Flutter. | ||||||
When overlaying Flutter widgets on top of `HtmlElementView`/`PlatformView` widgets that respond to mouse gestures (handle clicks, for example), the clicks will be consumed by the `HtmlElementView`/`PlatformView`, and not relayed to Flutter. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The result is that Flutter widget's `onTap` (and other) handlers won't fire as expected, but they'll affect the underlying webview. | ||||||
The result is that Flutter widget's `onTap` (and other) handlers won't fire as expected, but they'll affect the underlying native platform view. | ||||||
|
||||||
|The problem...| | ||||||
|:-:| | ||||||
|| | ||||||
|_In the dashed areas, mouse events won't work as expected. The `HtmlElementView` will consume them before Flutter sees them._| | ||||||
|
||||||
|
||||||
## How does this work? | ||||||
|
||||||
`PointerInterceptor` creates a platform view consisting of an empty HTML element. The element has the size of its `child` widget, and is inserted in the layer tree _behind_ its child in paint order. | ||||||
In web, `PointerInterceptor` creates a platform view consisting of an empty HTML element, while on iOS it creates an empty `UIView` instead. The element has the size of its `child` widget, and is inserted in the layer tree _behind_ its child in paint order. | ||||||
|
||||||
This empty platform view doesn't do anything with mouse events, other than preventing them from reaching other platform views underneath it. | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,6 @@ | |
/build/ | ||
|
||
# Web related | ||
lib/generated_plugin_registrant.dart | ||
|
||
# Symbolication related | ||
app.*.symbols | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,176 +4,13 @@ | |
|
||
// 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_example/main.dart' as app; | ||
|
||
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')); | ||
|
||
void main() { | ||
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); | ||
|
||
group('Without semantics', () { | ||
testWidgets( | ||
'on wrapped elements, the browser does not hit the background-html-view', | ||
(WidgetTester tester) async { | ||
app.main(); | ||
await tester.pumpAndSettle(); | ||
|
||
final html.Element element = | ||
_getHtmlElementAtCenter(clickableButtonFinder, tester); | ||
|
||
expect(element.id, isNot('background-html-view')); | ||
}, semanticsEnabled: false); | ||
|
||
testWidgets( | ||
'on wrapped elements with intercepting set to false, the browser hits the background-html-view', | ||
(WidgetTester tester) async { | ||
app.main(); | ||
await tester.pumpAndSettle(); | ||
|
||
final html.Element element = | ||
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester); | ||
|
||
expect(element.id, 'background-html-view'); | ||
}, semanticsEnabled: false); | ||
|
||
testWidgets( | ||
'on unwrapped elements, the browser hits the background-html-view', | ||
(WidgetTester tester) async { | ||
app.main(); | ||
await tester.pumpAndSettle(); | ||
|
||
final html.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(); | ||
|
||
final html.Element element = | ||
_getHtmlElementAt(tester.getTopLeft(backgroundFinder)); | ||
|
||
expect(element.id, 'background-html-view'); | ||
}, semanticsEnabled: false); | ||
}); | ||
|
||
group('With semantics', () { | ||
testWidgets('finds semantics of wrapped widgets', | ||
(WidgetTester tester) async { | ||
app.main(); | ||
await tester.pumpAndSettle(); | ||
|
||
if (!_newSemanticsAvailable()) { | ||
print('Skipping test: Needs flutter > 2.10'); | ||
return; | ||
} | ||
|
||
final html.Element element = | ||
_getHtmlElementAtCenter(clickableButtonFinder, tester); | ||
|
||
expect(element.tagName.toLowerCase(), 'flt-semantics'); | ||
expect(element.getAttribute('aria-label'), 'Works As Expected'); | ||
}); | ||
|
||
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; | ||
} | ||
|
||
final html.Element element = | ||
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester); | ||
|
||
expect(element.tagName.toLowerCase(), 'flt-semantics'); | ||
expect(element.getAttribute('aria-label'), | ||
'Never calls onPressed transparent'); | ||
}); | ||
|
||
testWidgets('finds semantics of unwrapped elements', | ||
(WidgetTester tester) async { | ||
app.main(); | ||
await tester.pumpAndSettle(); | ||
|
||
if (!_newSemanticsAvailable()) { | ||
print('Skipping test: Needs flutter > 2.10'); | ||
return; | ||
} | ||
|
||
final html.Element element = | ||
_getHtmlElementAtCenter(nonClickableButtonFinder, tester); | ||
|
||
expect(element.tagName.toLowerCase(), 'flt-semantics'); | ||
expect(element.getAttribute('aria-label'), 'Never calls onPressed'); | ||
}); | ||
|
||
// Notice that, when hit-testing the background platform view, instead of | ||
// finding a semantics node, the platform view itself is found. This is | ||
// because the platform view does not add interactive semantics nodes into | ||
// the framework's semantics tree. Instead, its semantics is determined by | ||
// the HTML content of the platform view itself. Flutter's semantics tree | ||
// 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(); | ||
|
||
final html.Element element = | ||
_getHtmlElementAt(tester.getTopLeft(backgroundFinder)); | ||
|
||
expect(element.id, 'background-html-view'); | ||
}); | ||
}); | ||
} | ||
|
||
// Calls [_getHtmlElementAt] passing it the center of the widget identified by | ||
// the `finder`. | ||
html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) { | ||
final Offset point = tester.getCenter(finder); | ||
return _getHtmlElementAt(point); | ||
} | ||
|
||
// Locates the DOM element at the given `point` using `elementFromPoint`. | ||
// | ||
// `elementFromPoint` is an approximate proxy for a hit test, although it's | ||
// 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) { | ||
// 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())!; | ||
} | ||
|
||
// 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; | ||
// TODO(louisehsu): given the difficulty of making the same integration tests | ||
// work for both web and ios implementations, please find tests in their respective | ||
// platform implementation packages. | ||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there are not going to be tests here, I'd fully remove You already have the |
||
testWidgets('placeholder test', (WidgetTester tester) async {}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
**/dgph | ||
*.mode1v3 | ||
*.mode2v3 | ||
*.moved-aside | ||
*.pbxuser | ||
*.perspectivev3 | ||
**/*sync/ | ||
.sconsign.dblite | ||
.tags* | ||
**/.vagrant/ | ||
**/DerivedData/ | ||
Icon? | ||
**/Pods/ | ||
**/.symlinks/ | ||
profile | ||
xcuserdata | ||
**/.generated/ | ||
Flutter/App.framework | ||
Flutter/Flutter.framework | ||
Flutter/Flutter.podspec | ||
Flutter/Generated.xcconfig | ||
Flutter/ephemeral/ | ||
Flutter/app.flx | ||
Flutter/app.zip | ||
Flutter/flutter_assets/ | ||
Flutter/flutter_export_environment.sh | ||
ServiceDefinitions.json | ||
Runner/GeneratedPluginRegistrant.* | ||
|
||
# Exceptions to above rules. | ||
!default.mode1v3 | ||
!default.mode2v3 | ||
!default.pbxuser | ||
!default.perspectivev3 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>App</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>io.flutter.flutter.app</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>App</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleSignature</key> | ||
<string>????</string> | ||
<key>CFBundleVersion</key> | ||
<string>1.0</string> | ||
<key>MinimumOSVersion</key> | ||
<string>11.0</string> | ||
</dict> | ||
</plist> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" | ||
#include "Generated.xcconfig" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" | ||
#include "Generated.xcconfig" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Uncomment this line to define a global platform for your project | ||
# platform :ios, '11.0' | ||
|
||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. | ||
ENV['COCOAPODS_DISABLE_STATS'] = 'true' | ||
|
||
project 'Runner', { | ||
'Debug' => :debug, | ||
'Profile' => :release, | ||
'Release' => :release, | ||
} | ||
|
||
def flutter_root | ||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) | ||
unless File.exist?(generated_xcode_build_settings_path) | ||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" | ||
end | ||
|
||
File.foreach(generated_xcode_build_settings_path) do |line| | ||
matches = line.match(/FLUTTER_ROOT\=(.*)/) | ||
return matches[1].strip if matches | ||
end | ||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" | ||
end | ||
|
||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) | ||
|
||
flutter_ios_podfile_setup | ||
|
||
target 'Runner' do | ||
use_frameworks! | ||
use_modular_headers! | ||
|
||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | ||
end | ||
|
||
post_install do |installer| | ||
installer.pods_project.targets.each do |target| | ||
flutter_additional_ios_build_settings(target) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.