mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This concludes step 1 of the `HtmlElementView` improvements. It's now possible to pass creation params to platform view factories directly from `HtmlElementView`.
Here's a sample app using a single factory to render platform views in different colors:
<details>
<summary>Code sample</summary>
```dart
import 'dart:js_interop';
import 'dart:ui_web' as ui_web;
import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Platform View Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Platform View Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
BoxWrapper('red'),
BoxWrapper(null),
BoxWrapper('blue'),
],
),
),
),
);
}
}
bool isRegistered = false;
class BoxWrapper extends StatelessWidget {
const BoxWrapper(this.cssColor);
final String? cssColor;
void register() {
if (isRegistered) return;
isRegistered = true;
ui_web.platformViewRegistry.registerViewFactory('my-platform-view', (
id, {
Object? params,
}) {
params as String?;
final element = web.document.createElement('div'.toJS) as web.HTMLElement;
element.textContent = 'Platform View'.toJS;
element.style
..lineHeight = '100px'.toJS
..fontSize = '24px'.toJS
..backgroundColor = (params ?? 'pink').toJS
..textAlign = 'center'.toJS;
return element;
});
}
@override
Widget build(BuildContext context) {
register();
return SizedBox(
width: 200,
height: 100,
child: Card(
child: HtmlElementView(
viewType: 'my-platform-view',
creationParams: cssColor,
),
),
);
}
}
```
</details>

Depends on https://github.com/flutter/engine/pull/42255
Part of https://github.com/flutter/flutter/issues/127030
282 lines
8.4 KiB
Dart
282 lines
8.4 KiB
Dart
// 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.
|
|
|
|
@TestOn('chrome')
|
|
library;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../services/fake_platform_views.dart';
|
|
|
|
void main() {
|
|
group('HtmlElementView', () {
|
|
testWidgets('Create HTML view', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Create HTML view with PlatformViewCreatedCallback', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
|
|
bool hasPlatformViewCreated = false;
|
|
void onPlatformViewCreatedCallBack(int id) {
|
|
hasPlatformViewCreated = true;
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(
|
|
viewType: 'webview',
|
|
onPlatformViewCreated: onPlatformViewCreatedCallBack,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Check the onPlatformViewCreatedCallBack has been called.
|
|
expect(hasPlatformViewCreated, true);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Create HTML view with creation params', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
await tester.pumpWidget(
|
|
const Column(
|
|
children: <Widget>[
|
|
SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(
|
|
viewType: 'webview',
|
|
creationParams: 'foobar',
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(
|
|
viewType: 'webview',
|
|
creationParams: 123,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 1, 'webview', 'foobar'),
|
|
FakeHtmlPlatformView(currentViewId + 2, 'webview', 123),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Resize HTML view', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview'),
|
|
),
|
|
),
|
|
);
|
|
|
|
viewsController.resizeCompleter = Completer<void>();
|
|
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 100.0,
|
|
height: 50.0,
|
|
child: HtmlElementView(viewType: 'webview'),
|
|
),
|
|
),
|
|
);
|
|
|
|
viewsController.resizeCompleter.complete();
|
|
await tester.pump();
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Change HTML view type', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
viewsController.registerViewType('maps');
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview'),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'maps'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 2, 'maps'),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Dispose HTML view', (WidgetTester tester) async {
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview'),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
const Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
isEmpty,
|
|
);
|
|
});
|
|
|
|
testWidgets('HTML view survives widget tree change', (WidgetTester tester) async {
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
final GlobalKey key = GlobalKey();
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview', key: key),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(viewType: 'webview', key: key),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
viewsController.views,
|
|
unorderedEquals(<FakeHtmlPlatformView>[
|
|
FakeHtmlPlatformView(currentViewId + 1, 'webview'),
|
|
]),
|
|
);
|
|
});
|
|
|
|
testWidgets('HtmlElementView has correct semantics', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
|
expect(currentViewId, greaterThanOrEqualTo(0));
|
|
final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController();
|
|
viewsController.registerViewType('webview');
|
|
|
|
await tester.pumpWidget(
|
|
Semantics(
|
|
container: true,
|
|
child: const Align(
|
|
alignment: Alignment.bottomRight,
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 100.0,
|
|
child: HtmlElementView(
|
|
viewType: 'webview',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
// First frame is before the platform view was created so the render object
|
|
// is not yet in the tree.
|
|
await tester.pump();
|
|
|
|
// The platform view ID is set on the child of the HtmlElementView render object.
|
|
final SemanticsNode semantics = tester.getSemantics(find.byType(PlatformViewSurface));
|
|
|
|
expect(semantics.platformViewId, currentViewId + 1);
|
|
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
|
|
// A 200x100 rect positioned at bottom right of a 800x600 box.
|
|
expect(semantics.transform, Matrix4.translationValues(600, 500, 0));
|
|
expect(semantics.childrenCount, 0);
|
|
|
|
handle.dispose();
|
|
});
|
|
});
|
|
}
|