Unverified Commit 55fe41be authored by Mouad Debbar's avatar Mouad Debbar Committed by GitHub

[web] New HtmlElementView.fromTagName constructor (#130513)

This wraps up the platform view improvements discussed in https://github.com/flutter/flutter/issues/127030.

- Splits `HtmlElementView` into 2 files that are conditionally imported.
- The non-web version can be instantiated but it throws if it ends up being built in a widget tree.
- Out-of-the-box view factories that create visible & invisible DOM elements given a `tagName` parameter.
- New `HtmlElementView.fromTagName()` constructor that uses the default factories to create DOM elements.
- Tests covering the new API.

Depends on https://github.com/flutter/engine/pull/43828

Fixes https://github.com/flutter/flutter/issues/127030
parent f054f5aa
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: prefer_const_constructors_in_immutables
// ignore_for_file: avoid_unused_constructor_parameters
import 'framework.dart';
import 'platform_view.dart';
/// The platform-specific implementation of [HtmlElementView].
extension HtmlElementViewImpl on HtmlElementView {
/// Creates an [HtmlElementView] that renders a DOM element with the given
/// [tagName].
static HtmlElementView createFromTagName({
Key? key,
required String tagName,
bool isVisible = true,
ElementCreatedCallback? onElementCreated,
}) {
throw UnimplementedError('HtmlElementView is only available on Flutter Web');
}
/// Called from [HtmlElementView.build] to build the widget tree.
///
/// This is not expected to be invoked in non-web environments. It throws if
/// that happens.
///
/// The implementation on Flutter Web builds a platform view and handles its
/// lifecycle.
Widget buildImpl(BuildContext context) {
throw UnimplementedError('HtmlElementView is only available on Flutter Web');
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui_web' as ui_web;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'framework.dart';
import 'platform_view.dart';
/// The platform-specific implementation of [HtmlElementView].
extension HtmlElementViewImpl on HtmlElementView {
/// Creates an [HtmlElementView] that renders a DOM element with the given
/// [tagName].
static HtmlElementView createFromTagName({
Key? key,
required String tagName,
bool isVisible = true,
ElementCreatedCallback? onElementCreated,
}) {
return HtmlElementView(
key: key,
viewType: isVisible ? ui_web.PlatformViewRegistry.defaultVisibleViewType : ui_web.PlatformViewRegistry.defaultInvisibleViewType,
onPlatformViewCreated: _createPlatformViewCallbackForElementCallback(onElementCreated),
creationParams: <dynamic, dynamic>{'tagName': tagName},
);
}
/// The implementation of [HtmlElementView.build].
///
/// This is not expected to be invoked in non-web environments. It throws if
/// that happens.
///
/// The implementation on Flutter Web builds an HTML platform view and handles
/// its lifecycle.
Widget buildImpl(BuildContext context) {
return PlatformViewLink(
viewType: viewType,
onCreatePlatformView: _createController,
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
);
}
/// Creates the controller and kicks off its initialization.
_HtmlElementViewController _createController(
PlatformViewCreationParams params,
) {
final _HtmlElementViewController controller = _HtmlElementViewController(
params.id,
viewType,
creationParams,
);
controller._initialize().then((_) {
params.onPlatformViewCreated(params.id);
onPlatformViewCreated?.call(params.id);
});
return controller;
}
}
PlatformViewCreatedCallback? _createPlatformViewCallbackForElementCallback(
ElementCreatedCallback? onElementCreated,
) {
if (onElementCreated == null) {
return null;
}
return (int id) {
onElementCreated(_platformViewsRegistry.getViewById(id));
};
}
class _HtmlElementViewController extends PlatformViewController {
_HtmlElementViewController(
this.viewId,
this.viewType,
this.creationParams,
);
@override
final int viewId;
/// The unique identifier for the HTML view type to be embedded by this widget.
///
/// A PlatformViewFactory for this type must have been registered.
final String viewType;
final dynamic creationParams;
bool _initialized = false;
Future<void> _initialize() async {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': viewType,
'params': creationParams,
};
await SystemChannels.platform_views.invokeMethod<void>('create', args);
_initialized = true;
}
@override
Future<void> clearFocus() async {
// Currently this does nothing on Flutter Web.
// TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
}
@override
Future<void> dispatchPointerEvent(PointerEvent event) async {
// We do not dispatch pointer events to HTML views because they may contain
// cross-origin iframes, which only accept user-generated events.
}
@override
Future<void> dispose() async {
if (_initialized) {
await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
}
}
}
/// Overrides the [ui_web.PlatformViewRegistry] used by [HtmlElementView].
///
/// This is used for testing view factory registration.
@visibleForTesting
ui_web.PlatformViewRegistry? debugOverridePlatformViewRegistry;
ui_web.PlatformViewRegistry get _platformViewsRegistry => debugOverridePlatformViewRegistry ?? ui_web.platformViewRegistry;
......@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import '_html_element_view_io.dart' if (dart.library.js_util) '_html_element_view_web.dart';
import 'basic.dart';
import 'debug.dart';
import 'focus_manager.dart';
......@@ -324,6 +325,14 @@ class UiKitView extends _DarwinView {
State<UiKitView> createState() => _UiKitViewState();
}
/// Callback signature for when the platform view's DOM element was created.
///
/// [element] is the DOM element that was created.
///
/// Also see [HtmlElementView.fromTagName] that uses this callback
/// signature.
typedef ElementCreatedCallback = void Function(Object element);
/// Embeds an HTML element in the Widget hierarchy in Flutter Web.
///
/// *NOTE*: This only works in Flutter Web. To embed web content on other
......@@ -368,6 +377,52 @@ class HtmlElementView extends StatelessWidget {
this.creationParams,
});
/// Creates a platform view that creates a DOM element specified by [tagName].
///
/// [isVisible] indicates whether the view is visible to the user or not.
/// Setting this to false allows the rendering pipeline to perform extra
/// optimizations knowing that the view will not result in any pixels painted
/// on the screen.
///
/// [onElementCreated] is called when the DOM element is created. It can be
/// used by the app to customize the element by adding attributes and styles.
///
/// ```dart
/// import 'package:flutter/widgets.dart';
/// import 'package:web/web.dart' as web;
///
/// // ...
///
/// class MyWidget extends StatelessWidget {
/// const MyWidget({super.key});
///
/// @override
/// Widget build(BuildContext context) {
/// return HtmlElementView.fromTagName(
/// tagName: 'div',
/// onElementCreated: (Object element) {
/// element as web.HTMLElement;
/// element.style
/// ..backgroundColor = 'blue'
/// ..border = '1px solid red';
/// },
/// );
/// }
/// }
/// ```
factory HtmlElementView.fromTagName({
Key? key,
required String tagName,
bool isVisible = true,
ElementCreatedCallback? onElementCreated,
}) =>
HtmlElementViewImpl.createFromTagName(
key: key,
tagName: tagName,
isVisible: isVisible,
onElementCreated: onElementCreated,
);
/// The unique identifier for the HTML view type to be embedded by this widget.
///
/// A PlatformViewFactory for this type must have been registered.
......@@ -382,83 +437,7 @@ class HtmlElementView extends StatelessWidget {
final Object? creationParams;
@override
Widget build(BuildContext context) {
assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.');
return PlatformViewLink(
viewType: viewType,
onCreatePlatformView: _createHtmlElementView,
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
);
}
/// Creates the controller and kicks off its initialization.
_HtmlElementViewController _createHtmlElementView(PlatformViewCreationParams params) {
final _HtmlElementViewController controller = _HtmlElementViewController(
params.id,
viewType,
creationParams,
);
controller._initialize().then((_) {
params.onPlatformViewCreated(params.id);
onPlatformViewCreated?.call(params.id);
});
return controller;
}
}
class _HtmlElementViewController extends PlatformViewController {
_HtmlElementViewController(
this.viewId,
this.viewType,
this.creationParams,
);
@override
final int viewId;
/// The unique identifier for the HTML view type to be embedded by this widget.
///
/// A PlatformViewFactory for this type must have been registered.
final String viewType;
final dynamic creationParams;
bool _initialized = false;
Future<void> _initialize() async {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': viewType,
'params': creationParams,
};
await SystemChannels.platform_views.invokeMethod<void>('create', args);
_initialized = true;
}
@override
Future<void> clearFocus() async {
// Currently this does nothing on Flutter Web.
// TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
}
@override
Future<void> dispatchPointerEvent(PointerEvent event) async {
// We do not dispatch pointer events to HTML views because they may contain
// cross-origin iframes, which only accept user-generated events.
}
@override
Future<void> dispose() async {
if (_initialized) {
await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
}
}
Widget build(BuildContext context) => buildImpl(context);
}
class _AndroidViewState extends State<AndroidView> {
......
......@@ -471,77 +471,6 @@ class FakeIosPlatformViewsController {
}
}
class FakeHtmlPlatformViewsController {
FakeHtmlPlatformViewsController() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform_views, _onMethodCall);
}
Iterable<FakeHtmlPlatformView> get views => _views.values;
final Map<int, FakeHtmlPlatformView> _views = <int, FakeHtmlPlatformView>{};
final Set<String> _registeredViewTypes = <String>{};
late Completer<void> resizeCompleter;
Completer<void>? createCompleter;
void registerViewType(String viewType) {
_registeredViewTypes.add(viewType);
}
Future<dynamic> _onMethodCall(MethodCall call) {
switch (call.method) {
case 'create':
return _create(call);
case 'dispose':
return _dispose(call);
}
return Future<dynamic>.sync(() => null);
}
Future<dynamic> _create(MethodCall call) async {
final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
final int id = args['id'] as int;
final String viewType = args['viewType'] as String;
final Object? params = args['params'];
if (_views.containsKey(id)) {
throw PlatformException(
code: 'error',
message: 'Trying to create an already created platform view, view id: $id',
);
}
if (!_registeredViewTypes.contains(viewType)) {
throw PlatformException(
code: 'error',
message: 'Trying to create a platform view of unregistered type: $viewType',
);
}
if (createCompleter != null) {
await createCompleter!.future;
}
_views[id] = FakeHtmlPlatformView(id, viewType, params);
return Future<int?>.sync(() => null);
}
Future<dynamic> _dispose(MethodCall call) {
final int id = call.arguments as int;
if (!_views.containsKey(id)) {
throw PlatformException(
code: 'error',
message: 'Trying to dispose a platform view with unknown id: $id',
);
}
_views.remove(id);
return Future<dynamic>.sync(() => null);
}
}
@immutable
class FakeAndroidPlatformView {
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection,
......@@ -656,31 +585,3 @@ class FakeUiKitView {
return 'FakeUiKitView(id: $id, type: $type, creationParams: $creationParams)';
}
}
@immutable
class FakeHtmlPlatformView {
const FakeHtmlPlatformView(this.id, this.type, [this.creationParams]);
final int id;
final String type;
final Object? creationParams;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is FakeHtmlPlatformView
&& other.id == id
&& other.type == type
&& other.creationParams == creationParams;
}
@override
int get hashCode => Object.hash(id, type, creationParams);
@override
String toString() {
return 'FakeHtmlPlatformView(id: $id, type: $type, params: $creationParams)';
}
}
......@@ -6,20 +6,45 @@
library;
import 'dart:async';
import 'dart:ui_web' as ui_web;
import 'package:collection/collection.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/widgets/_html_element_view_web.dart'
show debugOverridePlatformViewRegistry;
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:web/web.dart' as web;
import '../services/fake_platform_views.dart';
final Object _mockHtmlElement = Object();
Object _mockViewFactory(int id, {Object? params}) {
return _mockHtmlElement;
}
void main() {
late FakePlatformViewRegistry fakePlatformViewRegistry;
setUp(() {
fakePlatformViewRegistry = FakePlatformViewRegistry();
// Simulate the engine registering default factores.
fakePlatformViewRegistry.registerViewFactory(ui_web.PlatformViewRegistry.defaultVisibleViewType, (int viewId, {Object? params}) {
params!;
params as Map<Object?, Object?>;
return web.document.createElement(params['tagName']! as String);
});
fakePlatformViewRegistry.registerViewFactory(ui_web.PlatformViewRegistry.defaultInvisibleViewType, (int viewId, {Object? params}) {
params!;
params as Map<Object?, Object?>;
return web.document.createElement(params['tagName']! as String);
});
});
group('HtmlElementView', () {
testWidgets('Create HTML view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
......@@ -32,17 +57,16 @@ void main() {
);
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Create HTML view with PlatformViewCreatedCallback', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
bool hasPlatformViewCreated = false;
void onPlatformViewCreatedCallBack(int id) {
......@@ -66,17 +90,16 @@ void main() {
expect(hasPlatformViewCreated, true);
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Create HTML view with creation params', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Column(
children: <Widget>[
......@@ -101,18 +124,17 @@ void main() {
);
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 1, 'webview', 'foobar'),
FakeHtmlPlatformView(currentViewId + 2, 'webview', 123),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: 'foobar', htmlElement: _mockHtmlElement),
(id: currentViewId + 2, viewType: 'webview', params: 123, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Resize HTML view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(
......@@ -123,7 +145,7 @@ void main() {
),
);
viewsController.resizeCompleter = Completer<void>();
final Completer<void> resizeCompleter = Completer<void>();
await tester.pumpWidget(
const Center(
......@@ -135,22 +157,21 @@ void main() {
),
);
viewsController.resizeCompleter.complete();
resizeCompleter.complete();
await tester.pump();
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Change HTML view type', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.registerViewType('maps');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
fakePlatformViewRegistry.registerViewFactory('maps', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(
......@@ -172,16 +193,15 @@ void main() {
);
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 2, 'maps'),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 2, viewType: 'maps', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Dispose HTML view', (WidgetTester tester) async {
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(
......@@ -202,15 +222,14 @@ void main() {
);
expect(
viewsController.views,
fakePlatformViewRegistry.views,
isEmpty,
);
});
testWidgets('HTML view survives widget tree change', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Center(
......@@ -233,9 +252,9 @@ void main() {
);
expect(
viewsController.views,
unorderedEquals(<FakeHtmlPlatformView>[
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
......@@ -244,8 +263,7 @@ void main() {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
expect(currentViewId, greaterThanOrEqualTo(0));
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
viewsController.registerViewType('webview');
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
Semantics(
......@@ -278,4 +296,206 @@ void main() {
handle.dispose();
});
});
group('HtmlElementView.fromTagName', () {
setUp(() {
debugOverridePlatformViewRegistry = fakePlatformViewRegistry;
});
tearDown(() {
debugOverridePlatformViewRegistry = null;
});
testWidgets('Create platform view from tagName', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(tagName: 'div'),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(fakePlatformView.id, currentViewId + 1);
expect(fakePlatformView.viewType, ui_web.PlatformViewRegistry.defaultVisibleViewType);
expect(fakePlatformView.params, <dynamic, dynamic>{'tagName': 'div'});
// The HTML element should be a div.
final web.HTMLElement htmlElement = fakePlatformView.htmlElement as web.HTMLElement;
expect(htmlElement.tagName, equalsIgnoringCase('div'));
});
testWidgets('Create invisible platform view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(tagName: 'script', isVisible: false),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(fakePlatformView.id, currentViewId + 1);
// The view should be invisible.
expect(fakePlatformView.viewType, ui_web.PlatformViewRegistry.defaultInvisibleViewType);
expect(fakePlatformView.params, <dynamic, dynamic>{'tagName': 'script'});
// The HTML element should be a script.
final web.HTMLElement htmlElement = fakePlatformView.htmlElement as web.HTMLElement;
expect(htmlElement.tagName, equalsIgnoringCase('script'));
});
testWidgets('onElementCreated', (WidgetTester tester) async {
final List<Object> createdElements = <Object>[];
void onElementCreated(Object element) {
createdElements.add(element);
}
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(
tagName: 'table',
onElementCreated: onElementCreated,
),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(createdElements, hasLength(1));
final Object createdElement = createdElements.single;
expect(createdElement, fakePlatformView.htmlElement);
});
});
}
typedef FakeViewFactory = ({
String viewType,
bool isVisible,
Function viewFactory,
});
typedef FakePlatformView = ({
int id,
String viewType,
Object? params,
Object htmlElement,
});
class FakePlatformViewRegistry implements ui_web.PlatformViewRegistry {
FakePlatformViewRegistry() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform_views, _onMethodCall);
}
Set<FakePlatformView> get views => Set<FakePlatformView>.unmodifiable(_views);
final Set<FakePlatformView> _views = <FakePlatformView>{};
final Set<FakeViewFactory> _registeredViewTypes = <FakeViewFactory>{};
@override
bool registerViewFactory(String viewType, Function viewFactory, {bool isVisible = true}) {
if (_findRegisteredViewFactory(viewType) != null) {
return false;
}
_registeredViewTypes.add((
viewType: viewType,
isVisible: isVisible,
viewFactory: viewFactory,
));
return true;
}
@override
Object getViewById(int viewId) {
return _findViewById(viewId)!.htmlElement;
}
FakeViewFactory? _findRegisteredViewFactory(String viewType) {
return _registeredViewTypes.singleWhereOrNull(
(FakeViewFactory registered) => registered.viewType == viewType,
);
}
FakePlatformView? _findViewById(int viewId) {
return _views.singleWhereOrNull(
(FakePlatformView view) => view.id == viewId,
);
}
Future<dynamic> _onMethodCall(MethodCall call) {
switch (call.method) {
case 'create':
return _create(call);
case 'dispose':
return _dispose(call);
}
return Future<dynamic>.sync(() => null);
}
Future<dynamic> _create(MethodCall call) async {
final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
final int id = args['id'] as int;
final String viewType = args['viewType'] as String;
final Object? params = args['params'];
if (_findViewById(id) != null) {
throw PlatformException(
code: 'error',
message: 'Trying to create an already created platform view, view id: $id',
);
}
final FakeViewFactory? registered = _findRegisteredViewFactory(viewType);
if (registered == null) {
throw PlatformException(
code: 'error',
message: 'Trying to create a platform view of unregistered type: $viewType',
);
}
final ui_web.ParameterizedPlatformViewFactory viewFactory =
registered.viewFactory as ui_web.ParameterizedPlatformViewFactory;
_views.add((
id: id,
viewType: viewType,
params: params,
htmlElement: viewFactory(id, params: params),
));
return null;
}
Future<dynamic> _dispose(MethodCall call) async {
final int id = call.arguments as int;
final FakePlatformView? view = _findViewById(id);
if (view == null) {
throw PlatformException(
code: 'error',
message: 'Trying to dispose a platform view with unknown id: $id',
);
}
_views.remove(view);
return null;
}
}
......@@ -3189,7 +3189,7 @@ void main() {
// This file runs on non-web platforms, so we expect `HtmlElementView` to
// fail.
final dynamic exception = tester.takeException();
expect(exception, isAssertionError);
expect(exception, isUnimplementedError);
expect(exception.toString(), contains('HtmlElementView is only available on Flutter Web'));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment