[web] Only create one <style> for SelectableRegion (#161682)
We used to create and insert a new `<style>` element for each `SelectableRegion` widget. That's unnecessary. All we need is one `<style>` element that contains the style sheets that we want to apply. Most of this PR is re-working the tests to be able to check that the issue is actually fixed. Fixes https://github.com/flutter/flutter/issues/161519
This commit is contained in:
parent
093485d91c
commit
996badc9cf
@ -46,6 +46,12 @@ class PlatformSelectableRegionContextMenu extends StatelessWidget {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static RegisterViewFactory? debugOverrideRegisterViewFactory;
|
static RegisterViewFactory? debugOverrideRegisterViewFactory;
|
||||||
|
|
||||||
|
/// Resets the view factory registration to its initial state.
|
||||||
|
@visibleForTesting
|
||||||
|
static void debugResetRegistry() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => throw UnimplementedError();
|
Widget build(BuildContext context) => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,12 @@ class PlatformSelectableRegionContextMenu extends StatelessWidget {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static RegisterViewFactory? debugOverrideRegisterViewFactory;
|
static RegisterViewFactory? debugOverrideRegisterViewFactory;
|
||||||
|
|
||||||
|
/// Resets the view factory registration to its initial state.
|
||||||
|
@visibleForTesting
|
||||||
|
static void debugResetRegistry() {
|
||||||
|
_registeredViewType = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Registers the view factories for the interceptor widgets.
|
// Registers the view factories for the interceptor widgets.
|
||||||
static void _register() {
|
static void _register() {
|
||||||
assert(_registeredViewType == null);
|
assert(_registeredViewType == null);
|
||||||
@ -104,21 +110,21 @@ class PlatformSelectableRegionContextMenu extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String _registerWebSelectionCallback(_WebSelectionCallBack callback) {
|
static String _registerWebSelectionCallback(_WebSelectionCallBack callback) {
|
||||||
_registerViewFactory(_viewType, (int viewId) {
|
// Create css style for _kClassName.
|
||||||
|
final web.HTMLStyleElement styleElement =
|
||||||
|
web.document.createElement('style') as web.HTMLStyleElement;
|
||||||
|
web.document.head!.append(styleElement as JSAny);
|
||||||
|
final web.CSSStyleSheet sheet = styleElement.sheet!;
|
||||||
|
sheet.insertRule(_kClassRule, 0);
|
||||||
|
sheet.insertRule(_kClassSelectionRule, 1);
|
||||||
|
|
||||||
|
_registerViewFactory(_viewType, (int viewId, {Object? params}) {
|
||||||
final web.HTMLElement htmlElement = web.document.createElement('div') as web.HTMLElement;
|
final web.HTMLElement htmlElement = web.document.createElement('div') as web.HTMLElement;
|
||||||
htmlElement
|
htmlElement
|
||||||
..style.width = '100%'
|
..style.width = '100%'
|
||||||
..style.height = '100%'
|
..style.height = '100%'
|
||||||
..classList.add(_kClassName);
|
..classList.add(_kClassName);
|
||||||
|
|
||||||
// Create css style for _kClassName.
|
|
||||||
final web.HTMLStyleElement styleElement =
|
|
||||||
web.document.createElement('style') as web.HTMLStyleElement;
|
|
||||||
web.document.head!.append(styleElement as JSAny);
|
|
||||||
final web.CSSStyleSheet sheet = styleElement.sheet!;
|
|
||||||
sheet.insertRule(_kClassRule, 0);
|
|
||||||
sheet.insertRule(_kClassSelectionRule, 1);
|
|
||||||
|
|
||||||
htmlElement.addEventListener(
|
htmlElement.addEventListener(
|
||||||
'mousedown',
|
'mousedown',
|
||||||
(web.Event event) {
|
(web.Event event) {
|
||||||
|
@ -8,7 +8,6 @@ library;
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui_web' as ui_web;
|
import 'dart:ui_web' as ui_web;
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/src/widgets/_html_element_view_web.dart'
|
import 'package:flutter/src/widgets/_html_element_view_web.dart'
|
||||||
@ -17,6 +16,8 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
|
import 'web_platform_view_registry_utils.dart';
|
||||||
|
|
||||||
final Object _mockHtmlElement = Object();
|
final Object _mockHtmlElement = Object();
|
||||||
Object _mockViewFactory(int id, {Object? params}) {
|
Object _mockViewFactory(int id, {Object? params}) {
|
||||||
return _mockHtmlElement;
|
return _mockHtmlElement;
|
||||||
@ -419,101 +420,3 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
return switch (call.method) {
|
|
||||||
'create' => _create(call),
|
|
||||||
'dispose' => _dispose(call),
|
|
||||||
_ => 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,14 +5,15 @@
|
|||||||
@TestOn('browser') // This file contains web-only library.
|
@TestOn('browser') // This file contains web-only library.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:js_interop';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as web;
|
||||||
|
|
||||||
|
import 'web_platform_view_registry_utils.dart';
|
||||||
|
|
||||||
extension on web.HTMLCollection {
|
extension on web.HTMLCollection {
|
||||||
Iterable<web.Element?> get iterable =>
|
Iterable<web.Element?> get iterable =>
|
||||||
Iterable<web.Element?>.generate(length, (int index) => item(index));
|
Iterable<web.Element?>.generate(length, (int index) => item(index));
|
||||||
@ -24,47 +25,75 @@ extension on web.CSSRuleList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
web.HTMLElement? element;
|
late FakePlatformViewRegistry fakePlatformViewRegistry;
|
||||||
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = (
|
|
||||||
String viewType,
|
setUp(() {
|
||||||
Object Function(int viewId) fn, {
|
removeAllStyleElements();
|
||||||
bool isVisible = true,
|
fakePlatformViewRegistry = FakePlatformViewRegistry();
|
||||||
}) {
|
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory =
|
||||||
element = fn(0) as web.HTMLElement;
|
fakePlatformViewRegistry.registerViewFactory;
|
||||||
// The element needs to be attached to the document body to receive mouse
|
});
|
||||||
// events.
|
|
||||||
web.document.body!.append(element! as JSAny);
|
tearDown(() {
|
||||||
};
|
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = null;
|
||||||
// This force register the dom element.
|
PlatformSelectableRegionContextMenu.debugResetRegistry();
|
||||||
PlatformSelectableRegionContextMenu(child: const Placeholder());
|
});
|
||||||
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = null;
|
|
||||||
|
testWidgets('DOM element is set up correctly', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: SelectableRegion(
|
||||||
|
selectionControls: EmptyTextSelectionControls(),
|
||||||
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final web.HTMLElement element =
|
||||||
|
fakePlatformViewRegistry.getViewById(currentViewId + 1) as web.HTMLElement;
|
||||||
|
|
||||||
test('DOM element is set up correctly', () async {
|
|
||||||
expect(element, isNotNull);
|
expect(element, isNotNull);
|
||||||
expect(element!.style.width, '100%');
|
expect(element.style.width, '100%');
|
||||||
expect(element!.style.height, '100%');
|
expect(element.style.height, '100%');
|
||||||
expect(element!.classList.length, 1);
|
expect(element.classList.length, 1);
|
||||||
final String className = element!.className;
|
|
||||||
|
|
||||||
expect(web.document.head!.children.iterable, isNotEmpty);
|
final int numberOfStyleElements = getNumberOfStyleElements();
|
||||||
bool foundStyle = false;
|
expect(numberOfStyleElements, 1);
|
||||||
for (final web.Element? element in web.document.head!.children.iterable) {
|
});
|
||||||
expect(element, isNotNull);
|
|
||||||
if (element!.tagName != 'STYLE') {
|
testWidgets('only one <style> is inserted into the DOM', (WidgetTester tester) async {
|
||||||
continue;
|
await tester.pumpWidget(
|
||||||
}
|
MaterialApp(
|
||||||
final web.CSSRuleList? rules = (element as web.HTMLStyleElement).sheet?.rules;
|
home: ListView(
|
||||||
if (rules != null) {
|
children: <Widget>[
|
||||||
foundStyle = rules.iterable.any((web.CSSRule? rule) => rule!.cssText.contains(className));
|
SelectableRegion(
|
||||||
}
|
selectionControls: EmptyTextSelectionControls(),
|
||||||
if (foundStyle) {
|
child: const Placeholder(),
|
||||||
break;
|
),
|
||||||
}
|
SelectableRegion(
|
||||||
}
|
selectionControls: EmptyTextSelectionControls(),
|
||||||
expect(foundStyle, isTrue);
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
SelectableRegion(
|
||||||
|
selectionControls: EmptyTextSelectionControls(),
|
||||||
|
child: const Placeholder(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final int numberOfStyleElements = getNumberOfStyleElements();
|
||||||
|
expect(numberOfStyleElements, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('right click can trigger select word', (WidgetTester tester) async {
|
testWidgets('right click can trigger select word', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
addTearDown(focusNode.dispose);
|
addTearDown(focusNode.dispose);
|
||||||
final UniqueKey spy = UniqueKey();
|
final UniqueKey spy = UniqueKey();
|
||||||
@ -77,13 +106,16 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final web.HTMLElement element =
|
||||||
|
fakePlatformViewRegistry.getViewById(currentViewId + 1) as web.HTMLElement;
|
||||||
expect(element, isNotNull);
|
expect(element, isNotNull);
|
||||||
|
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Dispatch right click.
|
// Dispatch right click.
|
||||||
element!.dispatchEvent(
|
element.dispatchEvent(
|
||||||
web.MouseEvent('mousedown', web.MouseEventInit(button: 2, clientX: 200, clientY: 300)),
|
web.MouseEvent('mousedown', web.MouseEventInit(button: 2, clientX: 200, clientY: 300)),
|
||||||
);
|
);
|
||||||
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(
|
||||||
@ -104,6 +136,36 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void removeAllStyleElements() {
|
||||||
|
final List<web.Element?> styles = web.document.head!.children.iterable.toList();
|
||||||
|
for (final web.Element? element in styles) {
|
||||||
|
if (element!.tagName == 'STYLE') {
|
||||||
|
element.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getNumberOfStyleElements() {
|
||||||
|
expect(web.document.head!.children.iterable, isNotEmpty);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (final web.Element? element in web.document.head!.children.iterable) {
|
||||||
|
expect(element, isNotNull);
|
||||||
|
if (element!.tagName != 'STYLE') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final web.CSSRuleList? rules = (element as web.HTMLStyleElement).sheet?.rules;
|
||||||
|
if (rules != null) {
|
||||||
|
if (rules.iterable.any(
|
||||||
|
(web.CSSRule? rule) => rule!.cssText.contains('web-selectable-region-context-menu'),
|
||||||
|
)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
class SelectionSpy extends LeafRenderObjectWidget {
|
class SelectionSpy extends LeafRenderObjectWidget {
|
||||||
const SelectionSpy({super.key});
|
const SelectionSpy({super.key});
|
||||||
|
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
// 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:collection/collection.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return switch (call.method) {
|
||||||
|
'create' => _create(call),
|
||||||
|
'dispose' => _dispose(call),
|
||||||
|
_ => 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user