Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
weixin_43355755
engine
提交
b5f8141e
E
engine
项目概览
weixin_43355755
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
b5f8141e
编写于
5月 20, 2021
作者:
D
David Iglesias
提交者:
GitHub
5月 20, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[web] Render PlatformViews with SLOT tags. (#25747)
上级
acea7c42
变更
23
展开全部
隐藏空白更改
内联
并排
Showing
23 changed file
with
1117 addition
and
444 deletion
+1117
-444
ci/licenses_golden/licenses_flutter
ci/licenses_golden/licenses_flutter
+3
-0
lib/web_ui/lib/src/engine.dart
lib/web_ui/lib/src/engine.dart
+8
-0
lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
+67
-119
lib/web_ui/lib/src/engine/dom_renderer.dart
lib/web_ui/lib/src/engine/dom_renderer.dart
+51
-22
lib/web_ui/lib/src/engine/html/platform_view.dart
lib/web_ui/lib/src/engine/html/platform_view.dart
+4
-47
lib/web_ui/lib/src/engine/platform_dispatcher.dart
lib/web_ui/lib/src/engine/platform_dispatcher.dart
+9
-6
lib/web_ui/lib/src/engine/platform_views/content_manager.dart
...web_ui/lib/src/engine/platform_views/content_manager.dart
+155
-0
lib/web_ui/lib/src/engine/platform_views/message_handler.dart
...web_ui/lib/src/engine/platform_views/message_handler.dart
+144
-0
lib/web_ui/lib/src/engine/platform_views/slots.dart
lib/web_ui/lib/src/engine/platform_views/slots.dart
+46
-0
lib/web_ui/lib/src/engine/text/measurement.dart
lib/web_ui/lib/src/engine/text/measurement.dart
+21
-6
lib/web_ui/lib/src/engine/text_editing/text_editing.dart
lib/web_ui/lib/src/engine/text_editing/text_editing.dart
+9
-3
lib/web_ui/lib/ui.dart
lib/web_ui/lib/ui.dart
+7
-80
lib/web_ui/test/canvaskit/embedded_views_test.dart
lib/web_ui/test/canvaskit/embedded_views_test.dart
+59
-27
lib/web_ui/test/dom_renderer_test.dart
lib/web_ui/test/dom_renderer_test.dart
+5
-3
lib/web_ui/test/engine/platform_views/content_manager_test.dart
...b_ui/test/engine/platform_views/content_manager_test.dart
+148
-0
lib/web_ui/test/engine/platform_views/message_handler_test.dart
...b_ui/test/engine/platform_views/message_handler_test.dart
+181
-0
lib/web_ui/test/engine/platform_views/slots_test.dart
lib/web_ui/test/engine/platform_views/slots_test.dart
+41
-0
lib/web_ui/test/engine/semantics/semantics_test.dart
lib/web_ui/test/engine/semantics/semantics_test.dart
+21
-19
lib/web_ui/test/engine/semantics/semantics_tester.dart
lib/web_ui/test/engine/semantics/semantics_tester.dart
+17
-2
lib/web_ui/test/engine/semantics/text_field_test.dart
lib/web_ui/test/engine/semantics/text_field_test.dart
+32
-15
lib/web_ui/test/engine/surface/platform_view_test.dart
lib/web_ui/test/engine/surface/platform_view_test.dart
+3
-17
lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart
lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart
+3
-3
lib/web_ui/test/text_editing_test.dart
lib/web_ui/test/text_editing_test.dart
+83
-75
未找到文件。
ci/licenses_golden/licenses_flutter
浏览文件 @
b5f8141e
...
...
@@ -543,6 +543,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/slots.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart
...
...
lib/web_ui/lib/src/engine.dart
浏览文件 @
b5f8141e
...
...
@@ -248,6 +248,9 @@ part 'engine/onscreen_logging.dart';
part
'engine/picture.dart'
;
part
'engine/platform_dispatcher.dart'
;
part
'engine/platform_views.dart'
;
part
'engine/platform_views/content_manager.dart'
;
part
'engine/platform_views/message_handler.dart'
;
part
'engine/platform_views/slots.dart'
;
part
'engine/profiler.dart'
;
part
'engine/rrect_renderer.dart'
;
part
'engine/semantics/accessibility.dart'
;
...
...
@@ -413,6 +416,11 @@ class NullTreeSanitizer implements html.NodeTreeSanitizer {
void
sanitizeTree
(
html
.
Node
node
)
{}
}
/// The shared instance of PlatformViewManager shared across the engine to handle
/// rendering of PlatformViews into the web app.
/// TODO(dit): How to make this overridable from tests?
final
PlatformViewManager
platformViewManager
=
PlatformViewManager
();
/// Converts a matrix represented using [Float64List] to one represented using
/// [Float32List].
///
...
...
lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
浏览文件 @
b5f8141e
...
...
@@ -3,13 +3,11 @@
// found in the LICENSE file.
import
'dart:html'
as
html
;
import
'dart:typed_data'
;
import
'package:ui/src/engine.dart'
show
window
,
NullTreeSanitizer
;
import
'package:ui/src/engine.dart'
show
window
,
NullTreeSanitizer
,
platformViewManager
,
createPlatformViewSlot
;
import
'package:ui/ui.dart'
as
ui
;
import
'../html/path_to_svg_clip.dart'
;
import
'../services.dart'
;
import
'../util.dart'
;
import
'../vector_math.dart'
;
import
'canvas.dart'
;
...
...
@@ -37,11 +35,13 @@ class HtmlViewEmbedder {
final
Map
<
int
,
EmbeddedViewParams
>
_currentCompositionParams
=
<
int
,
EmbeddedViewParams
>{};
/// The HTML element associated with the given view id.
final
Map
<
int
?,
html
.
Element
>
_views
=
<
int
?,
html
.
Element
>{};
/// The root view in the stack of mutator elements for the view id.
final
Map
<
int
?,
html
.
Element
?>
_rootViews
=
<
int
?,
html
.
Element
?>{};
/// The clip chain for a view Id.
///
/// This contains:
/// * The root view in the stack of mutator elements for the view id.
/// * The slot view in the stack (what shows the actual platform view contents).
/// * The number of clipping elements used last time the view was composited.
final
Map
<
int
,
ViewClipChain
>
_viewClipChains
=
<
int
,
ViewClipChain
>{};
/// Surfaces used to draw on top of platform views, keyed by platform view ID.
///
...
...
@@ -51,18 +51,12 @@ class HtmlViewEmbedder {
/// The views that need to be recomposited into the scene on the next frame.
final
Set
<
int
>
_viewsToRecomposite
=
<
int
>{};
/// The views that need to be disposed of on the next frame.
final
Set
<
int
>
_viewsToDispose
=
<
int
>{};
/// The list of view ids that should be composited, in order.
List
<
int
>
_compositionOrder
=
<
int
>[];
/// The most recent composition order.
List
<
int
>
_activeCompositionOrder
=
<
int
>[];
/// The number of clipping elements used last time the view was composited.
Map
<
int
,
int
>
_clipCount
=
<
int
,
int
>{};
/// The size of the frame, in physical pixels.
ui
.
Size
_frameSize
=
ui
.
window
.
physicalSize
;
...
...
@@ -74,76 +68,6 @@ class HtmlViewEmbedder {
_frameSize
=
size
;
}
void
handlePlatformViewCall
(
ByteData
?
data
,
ui
.
PlatformMessageResponseCallback
?
callback
,
)
{
const
MethodCodec
codec
=
StandardMethodCodec
();
final
MethodCall
decoded
=
codec
.
decodeMethodCall
(
data
);
switch
(
decoded
.
method
)
{
case
'create'
:
_create
(
decoded
,
callback
);
return
;
case
'dispose'
:
_dispose
(
decoded
,
callback
!);
return
;
}
callback
!(
null
);
}
void
_create
(
MethodCall
methodCall
,
ui
.
PlatformMessageResponseCallback
?
callback
)
{
final
Map
<
dynamic
,
dynamic
>
args
=
methodCall
.
arguments
;
final
int
?
viewId
=
args
[
'id'
];
final
String
?
viewType
=
args
[
'viewType'
];
const
MethodCodec
codec
=
StandardMethodCodec
();
if
(
_views
[
viewId
]
!=
null
)
{
callback
!(
codec
.
encodeErrorEnvelope
(
code:
'recreating_view'
,
message:
'trying to create an already created view'
,
details:
'view id:
$viewId
'
,
));
return
;
}
final
ui
.
PlatformViewFactory
?
factory
=
ui
.
platformViewRegistry
.
registeredFactories
[
viewType
];
if
(
factory
==
null
)
{
callback
!(
codec
.
encodeErrorEnvelope
(
code:
'unregistered_view_type'
,
message:
'trying to create a view with an unregistered type'
,
details:
'unregistered view type:
$viewType
'
,
));
return
;
}
// TODO(het): Support creation parameters.
html
.
Element
embeddedView
=
factory
(
viewId
!);
_views
[
viewId
]
=
embeddedView
;
_rootViews
[
viewId
]
=
embeddedView
;
callback
!(
codec
.
encodeSuccessEnvelope
(
null
));
}
void
_dispose
(
MethodCall
methodCall
,
ui
.
PlatformMessageResponseCallback
callback
)
{
final
int
?
viewId
=
methodCall
.
arguments
;
const
MethodCodec
codec
=
StandardMethodCodec
();
if
(
viewId
==
null
||
!
_views
.
containsKey
(
viewId
))
{
callback
(
codec
.
encodeErrorEnvelope
(
code:
'unknown_view'
,
message:
'trying to dispose an unknown view'
,
details:
'view id:
$viewId
'
,
));
return
;
}
_viewsToDispose
.
add
(
viewId
);
callback
(
codec
.
encodeSuccessEnvelope
(
null
));
}
List
<
CkCanvas
>
getCurrentCanvases
()
{
final
List
<
CkCanvas
>
canvases
=
<
CkCanvas
>[];
for
(
int
i
=
0
;
i
<
_compositionOrder
.
length
;
i
++)
{
...
...
@@ -179,28 +103,39 @@ class HtmlViewEmbedder {
}
void
_compositeWithParams
(
int
viewId
,
EmbeddedViewParams
params
)
{
final
html
.
Element
platformView
=
_views
[
viewId
]!;
platformView
.
style
.
width
=
'
${params.size.width}
px'
;
platformView
.
style
.
height
=
'
${params.size.height}
px'
;
platformView
.
style
.
position
=
'absolute'
;
// If we haven't seen this viewId yet, cache it for clips/transforms.
ViewClipChain
clipChain
=
_viewClipChains
.
putIfAbsent
(
viewId
,
()
{
return
ViewClipChain
(
view:
createPlatformViewSlot
(
viewId
))
;
})
;
// <flt-scene-host> disables pointer events. Reenable them here because the
// underlying platform view would want to handle the pointer events.
platformView
.
style
.
pointerEvents
=
'auto'
;
html
.
Element
slot
=
clipChain
.
slot
;
// See `apply()` in the PersistedPlatformView class for the HTML version
// of this code.
slot
.
style
..
width
=
'
${params.size.width}
px'
..
height
=
'
${params.size.height}
px'
..
position
=
'absolute'
;
// Recompute the position in the DOM of the `slot` element...
final
int
currentClippingCount
=
_countClips
(
params
.
mutators
);
final
int
?
previousClippingCount
=
_clipCount
[
viewId
]
;
final
int
previousClippingCount
=
clipChain
.
clipCount
;
if
(
currentClippingCount
!=
previousClippingCount
)
{
_clipCount
[
viewId
]
=
currentClippingCount
;
html
.
Element
oldPlatformViewRoot
=
_rootViews
[
viewId
]!;
html
.
Element
?
newPlatformViewRoot
=
_reconstructClipViewsChain
(
html
.
Element
oldPlatformViewRoot
=
clipChain
.
root
;
html
.
Element
newPlatformViewRoot
=
_reconstructClipViewsChain
(
currentClippingCount
,
platformView
,
slot
,
oldPlatformViewRoot
,
);
_rootViews
[
viewId
]
=
newPlatformViewRoot
;
// Store the updated root element, and clip count
clipChain
.
updateClipChain
(
root:
newPlatformViewRoot
,
clipCount:
currentClippingCount
,
);
}
_applyMutators
(
params
.
mutators
,
platformView
,
viewId
);
// Apply mutators to the slot
_applyMutators
(
params
.
mutators
,
slot
,
viewId
);
}
int
_countClips
(
MutatorsStack
mutators
)
{
...
...
@@ -213,7 +148,7 @@ class HtmlViewEmbedder {
return
clipCount
;
}
html
.
Element
?
_reconstructClipViewsChain
(
html
.
Element
_reconstructClipViewsChain
(
int
numClips
,
html
.
Element
platformView
,
html
.
Element
headClipView
,
...
...
@@ -386,8 +321,6 @@ class HtmlViewEmbedder {
}
void
submitFrame
()
{
disposeViews
();
for
(
int
i
=
0
;
i
<
_compositionOrder
.
length
;
i
++)
{
int
viewId
=
_compositionOrder
[
i
];
_ensureOverlayInitialized
(
viewId
);
...
...
@@ -412,7 +345,7 @@ class HtmlViewEmbedder {
int
viewId
=
_compositionOrder
[
i
];
if
(
assertionsEnabled
)
{
if
(!
_views
.
containsKey
(
viewId
))
{
if
(!
platformViewManager
.
knowsViewId
(
viewId
))
{
debugInvalidViewIds
??=
<
int
>[];
debugInvalidViewIds
.
add
(
viewId
);
continue
;
...
...
@@ -420,7 +353,7 @@ class HtmlViewEmbedder {
}
unusedViews
.
remove
(
viewId
);
html
.
Element
platformViewRoot
=
_
rootViews
[
viewId
]!
;
html
.
Element
platformViewRoot
=
_
viewClipChains
[
viewId
]!.
root
;
html
.
Element
overlay
=
_overlays
[
viewId
]!.
htmlElement
;
platformViewRoot
.
remove
();
skiaSceneHost
!.
append
(
platformViewRoot
);
...
...
@@ -428,12 +361,10 @@ class HtmlViewEmbedder {
skiaSceneHost
!.
append
(
overlay
);
_activeCompositionOrder
.
add
(
viewId
);
}
_compositionOrder
.
clear
();
for
(
final
int
unusedViewId
in
unusedViews
)
{
_releaseOverlay
(
unusedViewId
);
_rootViews
[
unusedViewId
]?.
remove
();
}
disposeViews
(
unusedViews
);
if
(
assertionsEnabled
)
{
if
(
debugInvalidViewIds
!=
null
&&
debugInvalidViewIds
.
isNotEmpty
)
{
...
...
@@ -445,24 +376,18 @@ class HtmlViewEmbedder {
}
}
void
disposeViews
()
{
if
(
_viewsToDispose
.
isEmpty
)
{
return
;
}
for
(
final
int
viewId
in
_viewsToDispose
)
{
final
html
.
Element
rootView
=
_rootViews
[
viewId
]!;
rootView
.
remove
();
_views
.
remove
(
viewId
);
_rootViews
.
remove
(
viewId
);
void
disposeViews
(
Set
<
int
>
viewsToDispose
)
{
for
(
final
int
viewId
in
viewsToDispose
)
{
// Remove viewId from the _viewClipChains Map, and then from the DOM.
ViewClipChain
clipChain
=
_viewClipChains
.
remove
(
viewId
)!;
clipChain
.
root
.
remove
();
// More cleanup
_releaseOverlay
(
viewId
);
_currentCompositionParams
.
remove
(
viewId
);
_clipCount
.
remove
(
viewId
);
_viewsToRecomposite
.
remove
(
viewId
);
_cleanUpClipDefs
(
viewId
);
_svgClipDefs
.
remove
(
viewId
);
}
_viewsToDispose
.
clear
();
}
void
_releaseOverlay
(
int
viewId
)
{
...
...
@@ -499,6 +424,29 @@ class HtmlViewEmbedder {
}
}
/// Represents a Clip Chain (for a view).
///
/// Objects of this class contain:
/// * The root view in the stack of mutator elements for the view id.
/// * The slot view in the stack (the actual contents of the platform view).
/// * The number of clipping elements used last time the view was composited.
class
ViewClipChain
{
html
.
Element
_root
;
html
.
Element
_slot
;
int
_clipCount
=
-
1
;
ViewClipChain
({
required
html
.
Element
view
})
:
this
.
_root
=
view
,
this
.
_slot
=
view
;
html
.
Element
get
root
=>
_root
;
html
.
Element
get
slot
=>
_slot
;
int
get
clipCount
=>
_clipCount
;
void
updateClipChain
({
required
html
.
Element
root
,
required
int
clipCount
})
{
_root
=
root
;
_clipCount
=
clipCount
;
}
}
/// Caches surfaces used to overlay platform views.
class
OverlayCache
{
static
const
int
kDefaultCacheSize
=
5
;
...
...
lib/web_ui/lib/src/engine/dom_renderer.dart
浏览文件 @
b5f8141e
...
...
@@ -12,7 +12,10 @@ class DomRenderer {
reset
();
TextMeasurementService
.
initialize
(
rulerCacheCapacity:
10
);
TextMeasurementService
.
initialize
(
rulerCacheCapacity:
10
,
root:
_glassPaneShadow
!,
);
assert
(()
{
_setupHotRestart
();
...
...
@@ -26,6 +29,9 @@ class DomRenderer {
static
const
int
vibrateHeavyImpact
=
30
;
static
const
int
vibrateSelectionClick
=
10
;
// The tag name for the root view of the flutter app (glass-pane)
static
const
String
_glassPaneTagName
=
'flt-glass-pane'
;
/// Fires when browser language preferences change.
static
const
html
.
EventStreamProvider
<
html
.
Event
>
languageChangeEvent
=
const
html
.
EventStreamProvider
<
html
.
Event
>(
'languagechange'
);
...
...
@@ -154,6 +160,10 @@ class DomRenderer {
html
.
Element
?
get
glassPaneElement
=>
_glassPaneElement
;
html
.
Element
?
_glassPaneElement
;
/// The ShadowRoot of the [glassPaneElement].
html
.
ShadowRoot
?
get
glassPaneShadow
=>
_glassPaneShadow
;
html
.
ShadowRoot
?
_glassPaneShadow
;
final
html
.
Element
rootElement
=
html
.
document
.
body
!;
void
addElementClass
(
html
.
Element
element
,
String
className
)
{
...
...
@@ -252,11 +262,8 @@ class DomRenderer {
static
const
String
defaultCssFont
=
'
$defaultFontStyle
$defaultFontWeight
${defaultFontSize}
px
$defaultFontFamily
'
;
void
reset
()
{
_styleElement
?.
remove
();
_styleElement
=
html
.
StyleElement
();
html
.
document
.
head
!.
append
(
_styleElement
!);
final
html
.
CssStyleSheet
sheet
=
_styleElement
!.
sheet
as
html
.
CssStyleSheet
;
// Applies the required global CSS to an incoming [html.CssStyleSheet] `sheet`.
void
_applyCssRulesToSheet
(
html
.
CssStyleSheet
sheet
)
{
final
bool
isWebKit
=
browserEngine
==
BrowserEngine
.
webkit
;
final
bool
isFirefox
=
browserEngine
==
BrowserEngine
.
firefox
;
// TODO(butterfly): use more efficient CSS selectors; descendant selectors
...
...
@@ -341,7 +348,7 @@ flt-semantics [contentEditable="true"] {
// on using gray background. This CSS rule disables that.
if
(
isWebKit
)
{
sheet
.
insertRule
(
'''
flt-glass-pan
e * {
$_glassPaneTagNam
e
* {
-webkit-tap-highlight-color: transparent;
}
'''
,
sheet
.
cssRules
.
length
);
...
...
@@ -360,6 +367,16 @@ flt-glass-pane * {
}
'''
,
sheet
.
cssRules
.
length
);
}
}
void
reset
()
{
final
bool
isWebKit
=
browserEngine
==
BrowserEngine
.
webkit
;
_styleElement
?.
remove
();
_styleElement
=
html
.
StyleElement
();
html
.
document
.
head
!.
append
(
_styleElement
!);
final
html
.
CssStyleSheet
sheet
=
_styleElement
!.
sheet
as
html
.
CssStyleSheet
;
_applyCssRulesToSheet
(
sheet
);
final
html
.
BodyElement
bodyElement
=
html
.
document
.
body
!;
...
...
@@ -432,7 +449,7 @@ flt-glass-pane * {
// IMPORTANT: the glass pane element must come after the scene element in the DOM node list so
// it can intercept input events.
_glassPaneElement
?.
remove
();
final
html
.
Element
glassPaneElement
=
createElement
(
'flt-glass-pane'
);
final
html
.
Element
glassPaneElement
=
createElement
(
_glassPaneTagName
);
_glassPaneElement
=
glassPaneElement
;
glassPaneElement
.
style
..
position
=
'absolute'
...
...
@@ -440,9 +457,28 @@ flt-glass-pane * {
..
right
=
'0'
..
bottom
=
'0'
..
left
=
'0'
;
// Create a Shadow Root under the glass panel, and attach everything there,
// instead of directly underneath the glass panel.
final
html
.
ShadowRoot
glassPaneElementShadowRoot
=
glassPaneElement
.
attachShadow
(<
String
,
String
>{
'mode'
:
'open'
,
'delegatesFocus'
:
'true'
,
});
_glassPaneShadow
=
glassPaneElementShadowRoot
;
bodyElement
.
append
(
glassPaneElement
);
_sceneHostElement
=
createElement
(
'flt-scene-host'
);
final
html
.
StyleElement
shadowRootStyleElement
=
html
.
StyleElement
();
// The shadowRootStyleElement must be appended to the DOM, or its `sheet` will be null later...
glassPaneElementShadowRoot
.
append
(
shadowRootStyleElement
);
final
html
.
CssStyleSheet
shadowRootStyleSheet
=
shadowRootStyleElement
.
sheet
as
html
.
CssStyleSheet
;
_applyCssRulesToSheet
(
shadowRootStyleSheet
);
// TODO: Apply only rules for the shadow root
// Don't allow the scene to receive pointer events.
_sceneHostElement
=
createElement
(
'flt-scene-host'
)
..
style
.
pointerEvents
=
'none'
;
final
html
.
Element
semanticsHostElement
=
createElement
(
'flt-semantics-host'
);
...
...
@@ -451,23 +487,16 @@ flt-glass-pane * {
..
transformOrigin
=
'0 0 0'
;
_semanticsHostElement
=
semanticsHostElement
;
updateSemanticsScreenProperties
();
glassPaneElement
.
append
(
semanticsHostElement
);
// Don't allow the scene to receive pointer events.
_sceneHostElement
!.
style
.
pointerEvents
=
'none'
;
glassPaneElement
.
append
(
_sceneHostElement
!);
final
html
.
Element
_accesibilityPlaceholder
=
EngineSemanticsOwner
final
html
.
Element
_acces
s
ibilityPlaceholder
=
EngineSemanticsOwner
.
instance
.
semanticsHelper
.
prepareAccessibilityPlaceholder
();
// Insert the semantics placeholder after the scene host. For all widgets
// in the scene, except for platform widgets, the scene host will pass the
// pointer events through to the semantics tree. However, for platform
// views, the pointer events will not pass through, and will be handled
// by the platform view.
glassPaneElement
.
insertBefore
(
_accesibilityPlaceholder
,
_sceneHostElement
);
glassPaneElementShadowRoot
.
nodes
.
addAll
([
semanticsHostElement
,
_accessibilityPlaceholder
,
_sceneHostElement
!,
]);
// When debugging semantics, make the scene semi-transparent so that the
// semantics tree is visible.
...
...
lib/web_ui/lib/src/engine/html/platform_view.dart
浏览文件 @
b5f8141e
...
...
@@ -12,48 +12,11 @@ class PersistedPlatformView extends PersistedLeafSurface {
final
double
width
;
final
double
height
;
late
html
.
ShadowRoot
_shadowRoot
;
PersistedPlatformView
(
this
.
viewId
,
this
.
dx
,
this
.
dy
,
this
.
width
,
this
.
height
);
@override
html
.
Element
createElement
()
{
html
.
Element
element
=
defaultCreateElement
(
'flt-platform-view'
);
// Allow the platform view host element to receive pointer events.
//
// This is to allow platform view HTML elements to be interactive.
//
// ACCESSIBILITY NOTE: The way we enable accessibility on Flutter for web
// is to have a full-page button which waits for a double tap. Placing this
// full-page button in front of the scene would cause platform views not
// to receive pointer events. The tradeoff is that by placing the scene in
// front of the semantics placeholder will cause platform views to block
// pointer events from reaching the placeholder. This means that in order
// to enable accessibility, you must double tap the app *outside of a
// platform view*. As a consequence, a full-screen platform view will make
// it impossible to enable accessibility.
element
.
style
.
pointerEvents
=
'auto'
;
// Enforce the effective size of the PlatformView.
element
.
style
.
overflow
=
'hidden'
;
_shadowRoot
=
element
.
attachShadow
(<
String
,
String
>{
'mode'
:
'open'
});
final
html
.
StyleElement
_styleReset
=
html
.
StyleElement
();
_styleReset
.
innerHtml
=
'''
:host {
all: initial;
cursor: inherit;
}'''
;
_shadowRoot
.
append
(
_styleReset
);
final
html
.
Element
?
platformView
=
ui
.
platformViewRegistry
.
getCreatedView
(
viewId
);
if
(
platformView
!=
null
)
{
_shadowRoot
.
append
(
platformView
);
}
else
{
printWarning
(
'No platform view created for id
$viewId
'
);
}
return
element
;
return
createPlatformViewSlot
(
viewId
);
}
@override
...
...
@@ -61,18 +24,12 @@ class PersistedPlatformView extends PersistedLeafSurface {
@override
void
apply
()
{
// See `_compositeWithParams` in the HtmlViewEmbedder for the canvaskit equivalent.
rootElement
!.
style
..
transform
=
'translate(
${dx}
px,
${dy}
px)'
..
width
=
'
${width}
px'
..
height
=
'
${height}
px'
;
// Set size of the root element created by the PlatformView.
final
html
.
Element
?
platformView
=
ui
.
platformViewRegistry
.
getCreatedView
(
viewId
);
if
(
platformView
!=
null
)
{
platformView
.
style
..
width
=
'
${width}
px'
..
height
=
'
${height}
px'
;
}
..
height
=
'
${height}
px'
..
position
=
'absolute'
;
}
// Platform Views can only be updated if their viewId matches.
...
...
lib/web_ui/lib/src/engine/platform_dispatcher.dart
浏览文件 @
b5f8141e
...
...
@@ -314,6 +314,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
};
}
PlatformViewMessageHandler
?
_platformViewMessageHandler
;
void
_sendPlatformMessage
(
String
name
,
ByteData
?
data
,
...
...
@@ -449,12 +451,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
return
;
case
'flutter/platform_views'
:
if
(
useCanvasKit
)
{
rasterizer
!.
surface
.
viewEmbedder
.
handlePlatformViewCall
(
data
,
callback
);
}
else
{
ui
.
handlePlatformViewCall
(
data
!,
callback
!);
}
_platformViewMessageHandler
??=
PlatformViewMessageHandler
(
contentManager:
platformViewManager
,
contentHandler:
(
html
.
Element
content
)
{
domRenderer
.
glassPaneElement
!.
append
(
content
);
}
);
_platformViewMessageHandler
!.
handlePlatformViewCall
(
data
,
callback
!);
return
;
case
'flutter/accessibility'
:
...
...
lib/web_ui/lib/src/engine/platform_views/content_manager.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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.
part of
engine
;
/// A function which takes a unique `id` and some `params` and creates an HTML element.
///
/// This is made available to end-users through dart:ui in web.
typedef
ParameterizedPlatformViewFactory
=
html
.
Element
Function
(
int
viewId
,
{
Object
?
params
,
});
/// A function which takes a unique `id` and creates an HTML element.
///
/// This is made available to end-users through dart:ui in web.
typedef
PlatformViewFactory
=
html
.
Element
Function
(
int
viewId
);
/// This class handles the lifecycle of Platform Views in the DOM of a Flutter Web App.
///
/// There are three important parts of Platform Views. This class manages two of
/// them:
///
/// * `factories`: The functions used to render the contents of any given Platform
/// View by its `viewType`.
/// * `contents`: The result [html.Element] of calling a `factory` function.
///
/// The third part is `slots`, which are created on demand by the
/// [createPlatformViewSlot] function.
///
/// This class keeps a registry of `factories`, `contents` so the framework can
/// CRUD Platform Views as needed, regardless of the rendering backend.
class
PlatformViewManager
{
// The factory functions, indexed by the viewType
final
Map
<
String
,
Function
>
_factories
=
{};
// The references to content tags, indexed by their framework-given ID.
final
Map
<
int
,
html
.
Element
>
_contents
=
{};
/// Returns `true` if the passed in `viewType` has been registered before.
///
/// See [registerViewFactory] to understand how factories are registered.
bool
knowsViewType
(
String
viewType
)
{
return
_factories
.
containsKey
(
viewType
);
}
/// Returns `true` if the passed in `viewId` has been rendered (and not disposed) before.
///
/// See [renderContent] and [createPlatformViewSlot] to understand how platform views are
/// rendered.
bool
knowsViewId
(
int
viewId
)
{
return
_contents
.
containsKey
(
viewId
);
}
/// Registers a `factoryFunction` that knows how to render a Platform View of `viewType`.
///
/// `viewType` is selected by the programmer, but it can't be overridden once
/// it's been set.
///
/// `factoryFunction` needs to be a [PlatformViewFactory].
bool
registerFactory
(
String
viewType
,
Function
factoryFunction
)
{
assert
(
factoryFunction
is
PlatformViewFactory
||
factoryFunction
is
ParameterizedPlatformViewFactory
);
if
(
_factories
.
containsKey
(
viewType
))
{
return
false
;
}
_factories
[
viewType
]
=
factoryFunction
;
return
true
;
}
/// Creates the HTML markup for the `contents` of a Platform View.
///
/// The result of this call is cached in the `_contents` Map. This is only
/// cached so it can be disposed of later by [clearPlatformView]. _Note that
/// there's no `getContents` function in this class._
///
/// The resulting DOM for the `contents` of a Platform View looks like this:
///
/// ```html
/// <flt-platform-view slot="...">
/// <arbitrary-html-elements />
/// </flt-platform-view-slot>
/// ```
///
/// The `arbitrary-html-elements` are the result of the call to the user-supplied
/// `factory` function for this Platform View (see [registerFactory]).
///
/// The outer `flt-platform-view` tag is a simple wrapper that we add to have
/// a place where to attach the `slot` property, that will tell the browser
/// what `slot` tag will reveal this `contents`, **without modifying the returned
/// html from the `factory` function**.
html
.
Element
renderContent
(
String
viewType
,
int
viewId
,
Object
?
params
,
)
{
assert
(
knowsViewType
(
viewType
),
'Attempted to render contents of unregistered viewType:
$viewType
'
);
final
String
slotName
=
getPlatformViewSlotName
(
viewId
);
return
_contents
.
putIfAbsent
(
viewId
,
()
{
final
html
.
Element
wrapper
=
html
.
document
.
createElement
(
'flt-platform-view'
)
..
setAttribute
(
'slot'
,
slotName
);
final
Function
factoryFunction
=
_factories
[
viewType
]!;
late
html
.
Element
content
;
if
(
factoryFunction
is
ParameterizedPlatformViewFactory
)
{
content
=
factoryFunction
(
viewId
,
params:
params
);
}
else
{
content
=
factoryFunction
(
viewId
);
}
_ensureContentCorrectlySized
(
content
,
viewType
);
return
wrapper
..
append
(
content
);
});
}
/// Removes a PlatformView by its `viewId` from the manager, and from the DOM.
///
/// Once a view has been cleared, calls [knowsViewId] will fail, as if it had
/// never been rendered before.
void
clearPlatformView
(
int
viewId
)
{
// Remove from our cache, and then from the DOM...
_contents
.
remove
(
viewId
)?.
remove
();
}
/// Attempt to ensure that the contents of the user-supplied DOM element will
/// fill the space allocated for this platform view by the framework.
void
_ensureContentCorrectlySized
(
html
.
Element
content
,
String
viewType
)
{
// Scrutinize closely any other modifications to `content`.
// We shouldn't modify users' returned `content` if at all possible.
// Note there's also no getContent(viewId) function anymore, to prevent
// from later modifications too.
if
(
content
.
style
.
height
.
isEmpty
)
{
printWarning
(
'Height of Platform View type: [
$viewType
] may not be set.'
' Defaulting to `height: 100%`.
\n
'
'Set `style.height` to any appropriate value to stop this message.'
);
content
.
style
.
height
=
'100%'
;
}
if
(
content
.
style
.
width
.
isEmpty
)
{
printWarning
(
'Width of Platform View type: [
$viewType
] may not be set.'
' Defaulting to `width: 100%`.
\n
'
'Set `style.width` to any appropriate value to stop this message.'
);
content
.
style
.
width
=
'100%'
;
}
}
}
lib/web_ui/lib/src/engine/platform_views/message_handler.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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.
part of
engine
;
/// The signature for a callback for a Platform Message. From the `ui` package.
/// Copied here so there's no circular dependencies.
typedef
_PlatformMessageResponseCallback
=
void
Function
(
ByteData
?
data
);
/// A function that handle a newly created [html.Element] with the contents of a
/// platform view with a unique [int] id.
typedef
PlatformViewContentHandler
=
void
Function
(
html
.
Element
);
/// This class handles incoming framework messages to create/dispose Platform Views.
///
/// (An instance of this class is connected to the `flutter/platform_views`
/// Platform Channel in the [EnginePlatformDispatcher] class.)
///
/// It uses a [PlatformViewManager] to handle the CRUD of the DOM of Platform Views.
/// This `contentManager` is shared across the engine, to perform
/// all operations related to platform views (registration, rendering, etc...),
/// regardless of the rendering backend.
///
/// When the `contents` of a Platform View are created, a [PlatformViewContentHandler]
/// function (passed from the outside) will decide where in the DOM to inject
/// said content.
///
/// The rendering/compositing of Platform Views can create the other "half" of a
/// Platform View: the `slot`, through the [createPlatformViewSlot] method.
///
/// When a Platform View is disposed of, it is removed from the cache (and DOM)
/// directly by the `contentManager`. The canvaskit rendering backend needs to do
/// some extra cleanup of its internal state, but it can do it automatically. See
/// [HtmlViewEmbedder.disposeViews]
class
PlatformViewMessageHandler
{
final
MethodCodec
_codec
=
StandardMethodCodec
();
final
PlatformViewManager
_contentManager
;
final
PlatformViewContentHandler
?
_contentHandler
;
PlatformViewMessageHandler
({
required
PlatformViewManager
contentManager
,
PlatformViewContentHandler
?
contentHandler
,
})
:
this
.
_contentManager
=
contentManager
,
this
.
_contentHandler
=
contentHandler
;
/// Handle a `create` Platform View message.
///
/// This will attempt to render the `contents` and of a Platform View, if its
/// `viewType` has been registered previously.
///
/// (See [PlatformViewContentManager.registerFactory] for more details.)
///
/// The `contents` are delegated to a [_contentHandler] function, so the
/// active rendering backend can inject them in the right place of the DOM.
///
/// If all goes well, this function will `callback` with an empty success envelope.
/// In case of error, this will `callback` with an error envelope describing the error.
void
_createPlatformView
(
MethodCall
methodCall
,
_PlatformMessageResponseCallback
callback
,
)
{
final
Map
<
dynamic
,
dynamic
>
args
=
methodCall
.
arguments
;
final
int
viewId
=
args
[
'id'
];
final
String
viewType
=
args
[
'viewType'
];
if
(!
_contentManager
.
knowsViewType
(
viewType
))
{
callback
(
_codec
.
encodeErrorEnvelope
(
code:
'unregistered_view_type'
,
message:
'trying to create a view with an unregistered type'
,
details:
'unregistered view type:
$viewType
'
,
));
return
;
}
if
(
_contentManager
.
knowsViewId
(
viewId
))
{
callback
(
_codec
.
encodeErrorEnvelope
(
code:
'recreating_view'
,
message:
'trying to create an already created view'
,
details:
'view id:
$viewId
'
,
));
return
;
}
// TODO: How can users add extra `args` from the HtmlElementView widget?
final
html
.
Element
content
=
_contentManager
.
renderContent
(
viewType
,
viewId
,
args
,
);
// For now, we don't need anything fancier. If needed, this can be converted
// to a PlatformViewStrategy class for each web-renderer backend?
if
(
_contentHandler
!=
null
)
{
_contentHandler
!(
content
);
}
callback
(
_codec
.
encodeSuccessEnvelope
(
null
));
}
/// Handle a `dispose` Platform View message.
///
/// This will clear the cached information that the framework has about a given
/// `viewId`, through the [_contentManager].
///
/// Once that's done, the dispose call is delegated to the [_disposeHandler]
/// function, so the active rendering backend can dispose of whatever resources
/// it needed to get ahold of.
///
/// This function should always `callback` with an empty success envelope.
void
_disposePlatformView
(
MethodCall
methodCall
,
_PlatformMessageResponseCallback
callback
,
)
{
final
int
viewId
=
methodCall
.
arguments
;
// The contentManager removes the slot and the contents from its internal
// cache, and the DOM.
_contentManager
.
clearPlatformView
(
viewId
);
callback
(
_codec
.
encodeSuccessEnvelope
(
null
));
}
/// Handles a PlatformViewCall to the `flutter/platform_views` channel.
///
/// This method handles two possible messages:
/// * `create`: See [_createPlatformView]
/// * `dispose`: See [_disposePlatformView]
void
handlePlatformViewCall
(
ByteData
?
data
,
_PlatformMessageResponseCallback
callback
,
)
{
final
MethodCall
decoded
=
_codec
.
decodeMethodCall
(
data
);
switch
(
decoded
.
method
)
{
case
'create'
:
_createPlatformView
(
decoded
,
callback
);
return
;
case
'dispose'
:
_disposePlatformView
(
decoded
,
callback
);
return
;
}
callback
(
null
);
}
}
lib/web_ui/lib/src/engine/platform_views/slots.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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.
part of
engine
;
/// Returns the name of a slot from its `viewId`.
///
/// This is used by the [renderContent] function of the [PlatformViewManager]
/// class, and the [createPlatformViewSlot] method below, to keep the slot name
/// attribute consistent across the framework.
String
getPlatformViewSlotName
(
int
viewId
)
{
return
'flt-pv-slot-
$viewId
'
;
}
/// Creates the HTML markup for the `slot` of a Platform View.
///
/// The resulting DOM for a `slot` looks like this:
///
/// ```html
/// <flt-platform-view-slot style="...">
/// <slot name="..." />
/// </flt-platform-view-slot>
/// ```
///
/// The inner `SLOT` tag is standard HTML to reveal an element that is rendered
/// elsewhere in the DOM. Its `name` attribute must match the value of the `slot`
/// attribute of the contents being revealed (see [getPlatformViewSlotName].)
///
/// The outer `flt-platform-view-slot` tag is a simple wrapper that the framework
/// can position/style as needed.
///
/// (When the framework accesses a `slot`, it's really accessing its wrapper
/// `flt-platform-view-slot` tag)
html
.
Element
createPlatformViewSlot
(
int
viewId
)
{
final
String
slotName
=
getPlatformViewSlotName
(
viewId
);
final
html
.
Element
wrapper
=
html
.
document
.
createElement
(
'flt-platform-view-slot'
)
..
style
.
pointerEvents
=
'auto'
;
final
html
.
Element
slot
=
html
.
document
.
createElement
(
'slot'
)
..
setAttribute
(
'name'
,
slotName
);
return
wrapper
..
append
(
slot
);
}
lib/web_ui/lib/src/engine/text/measurement.dart
浏览文件 @
b5f8141e
...
...
@@ -22,9 +22,11 @@ bool _newlinePredicate(int char) {
prop
==
LineCharProperty
.
CR
;
}
/// Hosts ruler DOM elements in a hidden container.
/// Hosts ruler DOM elements in a hidden container under a `root` [html.Node].
///
/// The `root` [html.Node] is optional. Defaults to [domRenderer.glassPaneShadow].
class
RulerHost
{
RulerHost
()
{
RulerHost
(
{
html
.
Node
?
root
}
)
{
_rulerHost
.
style
..
position
=
'fixed'
..
visibility
=
'hidden'
...
...
@@ -33,7 +35,8 @@ class RulerHost {
..
left
=
'0'
..
width
=
'0'
..
height
=
'0'
;
html
.
document
.
body
!.
append
(
_rulerHost
);
(
root
??
domRenderer
.
glassPaneShadow
!).
append
(
_rulerHost
);
registerHotRestartListener
(
dispose
);
}
...
...
@@ -62,8 +65,14 @@ class RulerHost {
/// [ParagraphGeometricStyle].
///
/// All instances of [ParagraphRuler] should be created through this class.
///
/// An optional `root` [html.Node] can be passed, under which the DOM required
/// to perform measurements will be hosted.
class
RulerManager
extends
RulerHost
{
RulerManager
({
required
this
.
rulerCacheCapacity
}):
super
();
RulerManager
({
required
this
.
rulerCacheCapacity
,
html
.
Node
?
root
,
})
:
super
(
root:
root
);
final
int
rulerCacheCapacity
;
...
...
@@ -174,10 +183,16 @@ abstract class TextMeasurementService {
/// Initializes the text measurement service with a specific
/// [rulerCacheCapacity] that gets passed to the [RulerManager].
static
void
initialize
({
required
int
rulerCacheCapacity
})
{
///
/// An optional `root` [html.Node] can be passed, under which the DOM required
/// to perform measurements will be hosted. Defaults to [domRenderer.glassPaneShadow].
static
void
initialize
({
required
int
rulerCacheCapacity
,
html
.
Node
?
root
})
{
rulerManager
?.
dispose
();
rulerManager
=
null
;
rulerManager
=
RulerManager
(
rulerCacheCapacity:
rulerCacheCapacity
);
rulerManager
=
RulerManager
(
rulerCacheCapacity:
rulerCacheCapacity
,
root:
root
,
);
}
@visibleForTesting
...
...
lib/web_ui/lib/src/engine/text_editing/text_editing.dart
浏览文件 @
b5f8141e
...
...
@@ -26,6 +26,12 @@ const String transparentTextEditingClass = 'transparentTextEditing';
void
_emptyCallback
(
dynamic
_
)
{}
/// The default root that hosts all DOM required for text editing when a11y is not enabled.
///
/// This is something similar to [html.Document]. Currently, it's a [html.ShadowRoot].
@visibleForTesting
html
.
ShadowRoot
get
defaultTextEditingRoot
=>
domRenderer
.
glassPaneShadow
!;
/// These style attributes are constant throughout the life time of an input
/// element.
///
...
...
@@ -232,7 +238,7 @@ class EngineAutofillForm {
void
placeForm
(
html
.
HtmlElement
mainTextEditingElement
)
{
formElement
.
append
(
mainTextEditingElement
);
d
omRenderer
.
glassPaneElement
!
.
append
(
formElement
);
d
efaultTextEditingRoot
.
append
(
formElement
);
}
void
storeForm
()
{
...
...
@@ -832,7 +838,7 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
// DOM later, when the first location information arrived.
// Otherwise, on Blink based Desktop browsers, the autofill menu appears
// on top left of the screen.
d
omRenderer
.
glassPaneElement
!
.
append
(
activeDomElement
);
d
efaultTextEditingRoot
.
append
(
activeDomElement
);
_appendedToForm
=
false
;
}
...
...
@@ -1207,7 +1213,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
if
(
hasAutofillGroup
)
{
placeForm
();
}
else
{
d
omRenderer
.
glassPaneElement
!
.
append
(
activeDomElement
);
d
efaultTextEditingRoot
.
append
(
activeDomElement
);
}
inputConfig
.
textCapitalization
.
setAutocapitalizeAttribute
(
activeDomElement
);
}
...
...
lib/web_ui/lib/ui.dart
浏览文件 @
b5f8141e
...
...
@@ -58,93 +58,20 @@ void webOnlySetPluginHandler(Future<void> Function(String, ByteData?, PlatformMe
// does not allow exported non-migrated libraries from migrated libraries. When `dart:_engine`
// is migrated, we can move it back.
/// A function which takes a unique `id` and creates an HTML element.
typedef
PlatformViewFactory
=
html
.
Element
Function
(
int
viewId
);
/// A registry for factories that create platform views.
class
PlatformViewRegistry
{
final
Map
<
String
,
PlatformViewFactory
>
registeredFactories
=
<
String
,
PlatformViewFactory
>{};
final
Map
<
int
,
html
.
Element
>
_createdViews
=
<
int
,
html
.
Element
>{};
/// Private constructor so this class can be a singleton.
PlatformViewRegistry
.
_
();
/// Register [viewTypeId] as being creating by the given [factory].
bool
registerViewFactory
(
String
viewTypeId
,
PlatformViewFactory
factory
)
{
if
(
registeredFactories
.
containsKey
(
viewTypeId
))
{
return
false
;
}
registeredFactories
[
viewTypeId
]
=
factory
;
return
true
;
}
/// Returns the view that has been created with the given [id], or `null` if
/// no such view exists.
html
.
Element
?
getCreatedView
(
int
id
)
{
return
_createdViews
[
id
];
bool
registerViewFactory
(
String
viewTypeId
,
PlatformViewFactory
viewFactory
)
{
// TODO(web): Deprecate this once there's another way of calling `registerFactory` (js interop?)
return
engine
.
platformViewManager
.
registerFactory
(
viewTypeId
,
viewFactory
);
}
}
/// A function which takes a unique [id] and creates an HTML element.
typedef
PlatformViewFactory
=
html
.
Element
Function
(
int
viewId
);
/// The platform view registry for this app.
final
PlatformViewRegistry
platformViewRegistry
=
PlatformViewRegistry
.
_
();
/// Handles a platform call to `flutter/platform_views`.
///
/// Used to create platform views.
void
handlePlatformViewCall
(
ByteData
data
,
PlatformMessageResponseCallback
callback
,
)
{
const
engine
.
MethodCodec
codec
=
engine
.
StandardMethodCodec
();
final
engine
.
MethodCall
decoded
=
codec
.
decodeMethodCall
(
data
);
switch
(
decoded
.
method
)
{
case
'create'
:
_createPlatformView
(
decoded
,
callback
);
return
;
case
'dispose'
:
_disposePlatformView
(
decoded
,
callback
);
return
;
}
callback
(
null
);
}
void
_createPlatformView
(
engine
.
MethodCall
methodCall
,
PlatformMessageResponseCallback
callback
)
{
final
Map
<
dynamic
,
dynamic
>
args
=
methodCall
.
arguments
;
final
int
id
=
args
[
'id'
];
final
String
viewType
=
args
[
'viewType'
];
const
engine
.
MethodCodec
codec
=
engine
.
StandardMethodCodec
();
// TODO(het): Use 'direction', 'width', and 'height'.
final
PlatformViewFactory
?
platformViewFactory
=
platformViewRegistry
.
registeredFactories
[
viewType
];
if
(
platformViewFactory
==
null
)
{
callback
(
codec
.
encodeErrorEnvelope
(
code:
'Unregistered factory'
,
message:
"No factory registered for viewtype '
$viewType
'"
,
));
return
;
}
// TODO(het): Use creation parameters.
final
html
.
Element
element
=
platformViewFactory
(
id
);
platformViewRegistry
.
_createdViews
[
id
]
=
element
;
callback
(
codec
.
encodeSuccessEnvelope
(
null
));
}
void
_disposePlatformView
(
engine
.
MethodCall
methodCall
,
PlatformMessageResponseCallback
callback
)
{
final
int
id
=
methodCall
.
arguments
;
const
engine
.
MethodCodec
codec
=
engine
.
StandardMethodCodec
();
// Remove the root element of the view from the DOM.
platformViewRegistry
.
_createdViews
[
id
]?.
remove
();
platformViewRegistry
.
_createdViews
.
remove
(
id
);
callback
(
codec
.
encodeSuccessEnvelope
(
null
));
}
final
PlatformViewRegistry
platformViewRegistry
=
PlatformViewRegistry
();
// TODO(yjbanov): remove _Callback, _Callbacker, and _futurize. They are here only
// because the analyzer wasn't able to infer the correct types during
...
...
lib/web_ui/test/canvaskit/embedded_views_test.dart
浏览文件 @
b5f8141e
...
...
@@ -45,14 +45,25 @@ void testMain() {
sb
.
pushOffset
(
0
,
0
);
sb
.
addPlatformView
(
0
,
width:
10
,
height:
10
);
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!
.
querySelectorAll
(
'#view-0'
)
.
single
.
style
.
pointerEvents
,
'auto'
,
);
// The platform view is now split in two parts. The contents live
// as a child of the glassPane, and the slot lives in the glassPane
// shadow root. The slot is the one that has pointer events auto.
final
contents
=
domRenderer
.
glassPaneElement
!.
querySelector
(
'#view-0'
)!;
final
slot
=
domRenderer
.
sceneElement
!.
querySelector
(
'slot'
)!;
final
contentsHost
=
contents
.
parent
!;
final
slotHost
=
slot
.
parent
!;
expect
(
contents
,
isNotNull
,
reason:
'The view from the factory is injected in the DOM.'
);
expect
(
contentsHost
.
tagName
,
equalsIgnoringCase
(
'flt-platform-view'
));
expect
(
slotHost
.
tagName
,
equalsIgnoringCase
(
'flt-platform-view-slot'
));
expect
(
slotHost
.
style
.
pointerEvents
,
'auto'
,
reason:
'The slot reenables pointer events.'
);
expect
(
contentsHost
.
getAttribute
(
'slot'
),
slot
.
getAttribute
(
'name'
),
reason:
'The contents and slot are correctly related.'
);
});
test
(
'clips platform views with RRects'
,
()
async
{
...
...
@@ -69,6 +80,7 @@ void testMain() {
sb
.
pushClipRRect
(
ui
.
RRect
.
fromLTRBR
(
0
,
0
,
10
,
10
,
ui
.
Radius
.
circular
(
3
)));
sb
.
addPlatformView
(
0
,
width:
10
,
height:
10
);
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#sk_path_defs'
).
single
,
isNotNull
,
...
...
@@ -109,12 +121,12 @@ void testMain() {
sb
.
pushOffset
(
3
,
3
);
sb
.
addPlatformView
(
0
,
width:
10
,
height:
10
);
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
// Transformations happen on the slot element.
final
html
.
Element
slotHost
=
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
)!;
expect
(
domRenderer
.
sceneElement
!
.
querySelectorAll
(
'#view-0'
)
.
single
.
style
.
transform
,
slotHost
.
style
.
transform
,
// We should apply the scale matrix first, then the offset matrix.
// So the translate should be 515 (5 * 100 + 5 * 3), and not
// 503 (5 * 100 + 3).
...
...
@@ -150,11 +162,12 @@ void testMain() {
sb
.
pushOffset
(
3
,
3
);
sb
.
addPlatformView
(
0
,
width:
10
,
height:
10
);
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
final
html
.
Element
viewHost
=
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
).
single
;
// Transformations happen on the slot element.
final
html
.
Element
slotHost
=
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
)!;
expect
(
getTransformChain
(
view
Host
),
getTransformChain
(
slot
Host
),
<
String
>[
'matrix(0.25, 0, 0, 0.25, 1.5, 1.5)'
],
);
});
...
...
@@ -177,11 +190,12 @@ void testMain() {
sb
.
pushOffset
(
9
,
9
);
sb
.
addPlatformView
(
0
,
width:
10
,
height:
10
);
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
final
html
.
Element
viewHost
=
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
).
single
;
// Transformations happen on the slot element.
final
html
.
Element
slotHost
=
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
)!;
expect
(
getTransformChain
(
view
Host
),
getTransformChain
(
slot
Host
),
<
String
>[
'matrix(1, 0, 0, 1, 9, 9)'
,
'matrix(1, 0, 0, 1, 6, 6)'
,
...
...
@@ -314,8 +328,12 @@ void testMain() {
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
),
hasLength
(
1
),
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
),
isNotNull
,
);
expect
(
domRenderer
.
glassPaneElement
!.
querySelector
(
'flt-platform-view'
),
isNotNull
,
);
await
_disposePlatformView
(
0
);
...
...
@@ -325,8 +343,12 @@ void testMain() {
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
),
hasLength
(
0
),
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
),
isNull
,
);
expect
(
domRenderer
.
glassPaneElement
!.
querySelector
(
'flt-platform-view'
),
isNull
,
);
});
...
...
@@ -346,8 +368,12 @@ void testMain() {
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
),
hasLength
(
1
),
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
),
isNotNull
,
);
expect
(
domRenderer
.
glassPaneElement
!.
querySelector
(
'flt-platform-view'
),
isNotNull
,
);
// Render a frame without a platform view, but also without disposing of
...
...
@@ -357,8 +383,14 @@ void testMain() {
dispatcher
.
rasterizer
!.
draw
(
sb
.
build
().
layerTree
);
expect
(
domRenderer
.
sceneElement
!.
querySelectorAll
(
'#view-0'
),
hasLength
(
0
),
domRenderer
.
sceneElement
!.
querySelector
(
'flt-platform-view-slot'
),
isNull
,
);
// The actual contents of the platform view are kept in the dom, until
// it's actually disposed of!
expect
(
domRenderer
.
glassPaneElement
!.
querySelector
(
'flt-platform-view'
),
isNotNull
,
);
});
...
...
lib/web_ui/test/dom_renderer_test.dart
浏览文件 @
b5f8141e
...
...
@@ -111,9 +111,11 @@ void testMain() {
browserEngine
==
BrowserEngine
.
edge
));
test
(
'accesibility placeholder is attached after creation'
,
()
{
DomRenderer
();
final
DomRenderer
renderer
=
DomRenderer
();
expect
(
html
.
document
.
getElementsByTagName
(
'flt-semantics-placeholder'
),
isNotEmpty
);
expect
(
renderer
.
glassPaneShadow
?.
querySelectorAll
(
'flt-semantics-placeholder'
),
isNotEmpty
,
);
});
}
lib/web_ui/test/engine/platform_views/content_manager_test.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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:html'
as
html
;
import
'package:ui/src/engine.dart'
;
import
'package:test/bootstrap/browser.dart'
;
import
'package:test/test.dart'
;
import
'../../matchers.dart'
;
void
main
(
)
{
internalBootstrapBrowserTest
(()
=>
testMain
);
}
void
testMain
(
)
{
group
(
'PlatformViewManager'
,
()
{
final
String
viewType
=
'forTest'
;
final
int
viewId
=
6
;
late
PlatformViewManager
contentManager
;
setUp
(()
{
contentManager
=
PlatformViewManager
();
});
group
(
'knowsViewType'
,
()
{
test
(
'recognizes viewTypes after registering them'
,
()
async
{
expect
(
contentManager
.
knowsViewType
(
viewType
),
isFalse
);
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
());
expect
(
contentManager
.
knowsViewType
(
viewType
),
isTrue
);
});
});
group
(
'knowsViewId'
,
()
{
test
(
'recognizes viewIds after *rendering* them'
,
()
async
{
expect
(
contentManager
.
knowsViewId
(
viewId
),
isFalse
);
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
());
expect
(
contentManager
.
knowsViewId
(
viewId
),
isFalse
);
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
expect
(
contentManager
.
knowsViewId
(
viewId
),
isTrue
);
});
test
(
'forgets viewIds after clearing them'
,
()
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
());
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
expect
(
contentManager
.
knowsViewId
(
viewId
),
isTrue
);
contentManager
.
clearPlatformView
(
viewId
);
expect
(
contentManager
.
knowsViewId
(
viewId
),
isFalse
);
});
});
group
(
'registerFactory'
,
()
{
test
(
'does NOT re-register factories'
,
()
async
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
()..
id
=
'pass'
);
// this should be rejected
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
SpanElement
()..
id
=
'fail'
);
final
html
.
Element
contents
=
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
expect
(
contents
.
querySelector
(
'#pass'
),
isNotNull
);
expect
(
contents
.
querySelector
(
'#fail'
),
isNull
,
reason:
'Factories cannot be overridden once registered'
);
});
});
group
(
'renderContent'
,
()
{
final
String
unregisteredViewType
=
'unregisteredForTest'
;
final
String
anotherViewType
=
'anotherViewType'
;
setUp
(()
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
{
return
html
.
DivElement
()..
setAttribute
(
'data-viewId'
,
'
$id
'
);
});
contentManager
.
registerFactory
(
anotherViewType
,
(
int
id
)
{
return
html
.
DivElement
()
..
setAttribute
(
'data-viewId'
,
'
$id
'
)
..
style
.
height
=
'auto'
..
style
.
width
=
'55%'
;
});
});
test
(
'refuse to render views for unregistered factories'
,
()
async
{
try
{
contentManager
.
renderContent
(
unregisteredViewType
,
viewId
,
null
);
fail
(
'renderContent should have thrown an Assertion error!'
);
}
catch
(
e
)
{
expect
(
e
,
isAssertionError
);
expect
((
e
as
AssertionError
).
message
,
contains
(
unregisteredViewType
));
}
});
test
(
'rendered markup contains required attributes'
,
()
async
{
final
html
.
Element
content
=
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
expect
(
content
.
getAttribute
(
'slot'
),
contains
(
'
$viewId
'
));
final
html
.
Element
userContent
=
content
.
querySelector
(
'div'
)!;
expect
(
userContent
.
style
.
height
,
'100%'
);
expect
(
userContent
.
style
.
width
,
'100%'
);
});
test
(
'slot property has the same value as createPlatformViewSlot'
,
()
async
{
final
html
.
Element
content
=
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
final
html
.
Element
slot
=
createPlatformViewSlot
(
viewId
);
final
html
.
Element
innerSlot
=
slot
.
querySelector
(
'slot'
)!;
expect
(
content
.
getAttribute
(
'slot'
),
innerSlot
.
getAttribute
(
'name'
),
reason:
'The slot attribute of the rendered content must match the name attribute of the SLOT of a given viewId'
);
});
test
(
'do not modify style.height / style.width if passed by the user (anotherViewType)'
,
()
async
{
final
html
.
Element
content
=
contentManager
.
renderContent
(
anotherViewType
,
viewId
,
null
);
final
html
.
Element
userContent
=
content
.
querySelector
(
'div'
)!;
expect
(
userContent
.
style
.
height
,
'auto'
);
expect
(
userContent
.
style
.
width
,
'55%'
);
});
test
(
'returns cached instances of already-rendered content'
,
()
async
{
final
html
.
Element
firstRender
=
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
final
html
.
Element
anotherRender
=
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
expect
(
firstRender
,
same
(
anotherRender
));
});
});
});
}
lib/web_ui/test/engine/platform_views/message_handler_test.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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:async'
;
import
'dart:html'
as
html
;
import
'dart:typed_data'
;
import
'package:ui/src/engine.dart'
;
import
'package:test/bootstrap/browser.dart'
;
import
'package:test/test.dart'
;
void
main
(
)
{
internalBootstrapBrowserTest
(()
=>
testMain
);
}
final
MethodCodec
codec
=
StandardMethodCodec
();
void
testMain
(
)
{
group
(
'PlatformViewMessageHandler'
,
()
{
group
(
'handlePlatformViewCall'
,
()
{
final
String
viewType
=
'forTest'
;
final
int
viewId
=
6
;
late
PlatformViewManager
contentManager
;
late
Completer
<
ByteData
?>
completer
;
late
Completer
<
html
.
Element
>
contentCompleter
;
setUp
(()
{
contentManager
=
PlatformViewManager
();
completer
=
Completer
<
ByteData
?>();
contentCompleter
=
Completer
<
html
.
Element
>();
});
group
(
'"create" message'
,
()
{
test
(
'unregistered viewType, fails with descriptive exception'
,
()
async
{
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
contentManager
,
);
final
ByteData
?
message
=
_getCreateMessage
(
viewType
,
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
ByteData
?
response
=
await
completer
.
future
;
try
{
codec
.
decodeEnvelope
(
response
!);
}
on
PlatformException
catch
(
e
)
{
expect
(
e
.
code
,
'unregistered_view_type'
);
expect
(
e
.
details
,
contains
(
viewType
));
}
});
test
(
'duplicate viewId, fails with descriptive exception'
,
()
async
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
());
contentManager
.
renderContent
(
viewType
,
viewId
,
null
);
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
contentManager
,
);
final
ByteData
?
message
=
_getCreateMessage
(
viewType
,
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
ByteData
?
response
=
await
completer
.
future
;
try
{
codec
.
decodeEnvelope
(
response
!);
}
on
PlatformException
catch
(
e
)
{
expect
(
e
.
code
,
'recreating_view'
);
expect
(
e
.
details
,
contains
(
'
$viewId
'
));
}
});
test
(
'returns a successEnvelope when the view is created normally'
,
()
async
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
()..
id
=
'success'
);
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
contentManager
,
);
final
ByteData
?
message
=
_getCreateMessage
(
viewType
,
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
ByteData
?
response
=
await
completer
.
future
;
expect
(
codec
.
decodeEnvelope
(
response
!),
isNull
,
reason:
'The response should be a success envelope, with null in it.'
);
});
test
(
'calls a contentHandler with the result of creating a view'
,
()
async
{
contentManager
.
registerFactory
(
viewType
,
(
int
id
)
=>
html
.
DivElement
()..
id
=
'success'
);
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
contentManager
,
contentHandler:
contentCompleter
.
complete
,
);
final
ByteData
?
message
=
_getCreateMessage
(
viewType
,
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
html
.
Element
contents
=
await
contentCompleter
.
future
;
final
ByteData
?
response
=
await
completer
.
future
;
expect
(
contents
.
querySelector
(
'div#success'
),
isNotNull
,
reason:
'The element created by the factory should be present in the created view.'
);
expect
(
codec
.
decodeEnvelope
(
response
!),
isNull
,
reason:
'The response should be a success envelope, with null in it.'
);
});
});
group
(
'"dispose" message'
,
()
{
late
Completer
<
int
>
viewIdCompleter
;
setUp
(()
{
viewIdCompleter
=
Completer
<
int
>();
});
test
(
'never fails, even for unknown viewIds'
,
()
async
{
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
contentManager
,
);
final
ByteData
?
message
=
_getDisposeMessage
(
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
ByteData
?
response
=
await
completer
.
future
;
expect
(
codec
.
decodeEnvelope
(
response
!),
isNull
,
reason:
'The response should be a success envelope, with null in it.'
);
});
test
(
'never fails, even for unknown viewIds'
,
()
async
{
final
messageHandler
=
PlatformViewMessageHandler
(
contentManager:
_FakePlatformViewManager
(
viewIdCompleter
.
complete
),
);
final
ByteData
?
message
=
_getDisposeMessage
(
viewId
);
messageHandler
.
handlePlatformViewCall
(
message
,
completer
.
complete
);
final
int
disposedViewId
=
await
viewIdCompleter
.
future
;
expect
(
disposedViewId
,
viewId
,
reason:
'The viewId to dispose should be passed to the contentManager'
);
});
});
});
});
}
class
_FakePlatformViewManager
extends
PlatformViewManager
{
_FakePlatformViewManager
(
void
Function
(
int
)
clearFunction
)
:
this
.
_clearPlatformView
=
clearFunction
;
void
Function
(
int
)
_clearPlatformView
;
@override
void
clearPlatformView
(
int
viewId
)
{
return
_clearPlatformView
(
viewId
);
}
}
ByteData
?
_getCreateMessage
(
String
viewType
,
int
viewId
)
{
return
codec
.
encodeMethodCall
(
MethodCall
(
'create'
,
{
'id'
:
viewId
,
'viewType'
:
viewType
,
},
));
}
ByteData
?
_getDisposeMessage
(
int
viewId
)
{
return
codec
.
encodeMethodCall
(
MethodCall
(
'dispose'
,
viewId
,
));
}
lib/web_ui/test/engine/platform_views/slots_test.dart
0 → 100644
浏览文件 @
b5f8141e
// Copyright 2013 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:html'
as
html
;
import
'package:ui/src/engine.dart'
;
import
'package:test/bootstrap/browser.dart'
;
import
'package:test/test.dart'
;
void
main
(
)
{
internalBootstrapBrowserTest
(()
=>
testMain
);
}
void
testMain
(
)
{
group
(
'PlatformViewManager'
,
()
{
final
int
viewId
=
6
;
group
(
'createPlatformViewSlot'
,
()
{
test
(
'can render slot, even for views that might have never been rendered before'
,
()
async
{
final
html
.
Element
slot
=
createPlatformViewSlot
(
viewId
);
expect
(
slot
,
isNotNull
);
expect
(
slot
.
querySelector
(
'slot'
),
isNotNull
);
});
test
(
'rendered markup contains required attributes'
,
()
async
{
final
html
.
Element
slot
=
createPlatformViewSlot
(
viewId
);
expect
(
slot
.
style
.
pointerEvents
,
'auto'
,
reason:
'Should re-enable pointer events for the contents of the view.'
);
final
html
.
Element
innerSlot
=
slot
.
querySelector
(
'slot'
)!;
expect
(
innerSlot
.
getAttribute
(
'name'
),
contains
(
'
$viewId
'
),
reason:
'The name attribute of the inner SLOT tag must refer to the viewId.'
);
});
});
});
}
lib/web_ui/test/engine/semantics/semantics_test.dart
浏览文件 @
b5f8141e
...
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.
6
// @dart = 2.
9
@TestOn
(
'chrome || safari || firefox'
)
import
'dart:async'
;
...
...
@@ -89,8 +89,10 @@ void _testEngineSemanticsOwner() {
// Synthesize a click on the placeholder.
final
html
.
Element
placeholder
=
html
.
document
.
querySelectorAll
(
'flt-semantics-placeholder'
).
single
;
appShadowRoot
.
querySelector
(
'flt-semantics-placeholder'
);
expect
(
placeholder
.
isConnected
,
true
);
final
html
.
Rectangle
<
num
>
rect
=
placeholder
.
getBoundingClientRect
();
placeholder
.
dispatchEvent
(
html
.
MouseEvent
(
'click'
,
...
...
@@ -113,7 +115,8 @@ void _testEngineSemanticsOwner() {
expect
(
semantics
().
semanticsEnabled
,
false
);
final
html
.
Element
placeholder
=
html
.
document
.
querySelectorAll
(
'flt-semantics-placeholder'
).
single
;
appShadowRoot
.
querySelector
(
'flt-semantics-placeholder'
);
expect
(
placeholder
.
isConnected
,
true
);
// Sending a semantics update should auto-enable engine semantics.
...
...
@@ -354,9 +357,9 @@ void _testContainer() {
</sem>'''
);
final
html
.
Element
parentElement
=
html
.
documen
t
.
querySelector
(
'flt-semantics'
);
appShadowRoo
t
.
querySelector
(
'flt-semantics'
);
final
html
.
Element
container
=
html
.
documen
t
.
querySelector
(
'flt-semantics-container'
);
appShadowRoo
t
.
querySelector
(
'flt-semantics-container'
);
if
(
isMacOrIOS
)
{
expect
(
parentElement
.
style
.
top
,
'0px'
);
...
...
@@ -402,9 +405,9 @@ void _testContainer() {
</sem>'''
);
final
html
.
Element
parentElement
=
html
.
documen
t
.
querySelector
(
'flt-semantics'
);
appShadowRoo
t
.
querySelector
(
'flt-semantics'
);
final
html
.
Element
container
=
html
.
documen
t
.
querySelector
(
'flt-semantics-container'
);
appShadowRoo
t
.
querySelector
(
'flt-semantics-container'
);
expect
(
parentElement
.
style
.
transform
,
'matrix(1, 0, 0, 1, 10, 10)'
);
expect
(
parentElement
.
style
.
transformOrigin
,
'0px 0px 0px'
);
...
...
@@ -446,10 +449,12 @@ void _testContainer() {
</sem-c>
</sem>'''
);
}
final
html
.
Element
parentElement
=
html
.
documen
t
.
querySelector
(
'flt-semantics'
);
appShadowRoo
t
.
querySelector
(
'flt-semantics'
);
final
html
.
Element
container
=
html
.
document
.
querySelector
(
'flt-semantics-container'
);
appShadowRoot
.
querySelector
(
'flt-semantics-container'
);
if
(
isMacOrIOS
)
{
expect
(
parentElement
.
style
.
top
,
'0px'
);
expect
(
parentElement
.
style
.
left
,
'0px'
);
...
...
@@ -804,8 +809,7 @@ void _testIncrementables() {
<input aria-valuenow="1" aria-valuetext="d" aria-valuemax="2" aria-valuemin="1">
</sem>'''
);
final
html
.
InputElement
input
=
html
.
document
.
querySelectorAll
(
'input'
).
single
;
final
html
.
InputElement
input
=
appShadowRoot
.
querySelector
(
'input'
);
input
.
value
=
'2'
;
input
.
dispatchEvent
(
html
.
Event
(
'change'
));
...
...
@@ -839,8 +843,7 @@ void _testIncrementables() {
<input aria-valuenow="1" aria-valuetext="d" aria-valuemax="1" aria-valuemin="0">
</sem>'''
);
final
html
.
InputElement
input
=
html
.
document
.
querySelectorAll
(
'input'
).
single
;
final
html
.
InputElement
input
=
appShadowRoot
.
querySelector
(
'input'
);
input
.
value
=
'0'
;
input
.
dispatchEvent
(
html
.
Event
(
'change'
));
...
...
@@ -933,20 +936,19 @@ void _testTextField() {
semantics
().
updateSemantics
(
builder
.
build
());
final
html
.
Element
textField
=
html
.
document
.
querySelectorAll
(
'input[data-semantics-role="text-field"]'
)
.
single
;
final
html
.
Element
textField
=
appShadowRoot
.
querySelector
(
'input[data-semantics-role="text-field"]'
);
expect
(
html
.
documen
t
.
activeElement
,
isNot
(
textField
));
expect
(
appShadowRoo
t
.
activeElement
,
isNot
(
textField
));
textField
.
focus
();
expect
(
html
.
documen
t
.
activeElement
,
textField
);
expect
(
appShadowRoo
t
.
activeElement
,
textField
);
expect
(
await
logger
.
idLog
.
first
,
0
);
expect
(
await
logger
.
actionLog
.
first
,
ui
.
SemanticsAction
.
tap
);
semantics
().
semanticsEnabled
=
false
;
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/46638
// TODO(nurhan): https://github.com/flutter/flutter/issues/50590
// TODO(nurhan): https://github.com/flutter/flutter/issues/50754
skip:
(
browserEngine
!=
BrowserEngine
.
blink
));
...
...
lib/web_ui/test/engine/semantics/semantics_tester.dart
浏览文件 @
b5f8141e
...
...
@@ -12,6 +12,21 @@ import 'package:ui/ui.dart' as ui;
import
'../../matchers.dart'
;
/// Gets the DOM host where the Flutter app is being rendered.
///
/// This function returns the correct host for the flutter app under testing,
/// so we don't have to hardcode html.document across the test. (The host of a
/// normal flutter app used to be html.document, but now that the app is wrapped
/// in a Shadow DOM, that's not the case anymore.)
///
/// A [html.ShadowRoot] quacks very similarly to a [html.Document], but
/// unfortunately they don't share any class/implement any interface that let us
/// use them interchangeably.
///
/// This flutterRoot can be changed to return ShadowRoot or Document without
/// the need to modify (most of) your code.
html
.
ShadowRoot
get
appShadowRoot
=>
domRenderer
.
glassPaneShadow
!;
/// CSS style applied to the root of the semantics tree.
// TODO(yjbanov): this should be handled internally by [expectSemanticsTree].
// No need for every test to inject it.
...
...
@@ -336,14 +351,14 @@ class SemanticsTester {
/// Verifies the HTML structure of the current semantics tree.
void
expectSemanticsTree
(
String
semanticsHtml
)
{
expect
(
canonicalizeHtml
(
html
.
documen
t
.
querySelector
(
'flt-semantics'
)!.
outerHtml
!),
canonicalizeHtml
(
appShadowRoo
t
.
querySelector
(
'flt-semantics'
)!.
outerHtml
!),
canonicalizeHtml
(
semanticsHtml
),
);
}
/// Finds the first HTML element in the semantics tree used for scrolling.
html
.
Element
?
findScrollable
()
{
return
html
.
documen
t
.
querySelectorAll
(
'flt-semantics'
).
cast
<
html
.
Element
?>().
firstWhere
(
return
appShadowRoo
t
.
querySelectorAll
(
'flt-semantics'
).
cast
<
html
.
Element
?>().
firstWhere
(
(
html
.
Element
?
element
)
=>
element
!.
style
.
overflow
==
'hidden'
||
element
.
style
.
overflowY
==
'scroll'
||
...
...
lib/web_ui/test/engine/semantics/text_field_test.dart
浏览文件 @
b5f8141e
...
...
@@ -75,15 +75,14 @@ void testMain() {
createTextFieldSemantics
(
value:
'hello'
);
final
html
.
Element
textField
=
html
.
document
.
querySelectorAll
(
'input[data-semantics-role="text-field"]'
)
.
single
;
final
html
.
Element
textField
=
appShadowRoot
.
querySelector
(
'input[data-semantics-role="text-field"]'
)!;
expect
(
html
.
documen
t
.
activeElement
,
isNot
(
textField
));
expect
(
appShadowRoo
t
.
activeElement
,
isNot
(
textField
));
textField
.
focus
();
expect
(
html
.
documen
t
.
activeElement
,
textField
);
expect
(
appShadowRoo
t
.
activeElement
,
textField
);
expect
(
await
logger
.
idLog
.
first
,
0
);
expect
(
await
logger
.
actionLog
.
first
,
ui
.
SemanticsAction
.
tap
);
...
...
@@ -99,6 +98,7 @@ void testMain() {
..
semanticsEnabled
=
true
;
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
int
changeCount
=
0
;
int
actionCount
=
0
;
...
...
@@ -121,8 +121,9 @@ void testMain() {
);
TextField
textField
=
textFieldSemantics
.
debugRoleManagerFor
(
Role
.
textField
)
as
TextField
;
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
expect
(
textField
.
editableElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
strategy
.
domElement
);
expect
((
textField
.
editableElement
as
dynamic
).
value
,
'hello'
);
expect
(
textField
.
editableElement
.
getAttribute
(
'aria-label'
),
'greeting'
);
expect
(
textField
.
editableElement
.
style
.
width
,
'10px'
);
...
...
@@ -137,6 +138,7 @@ void testMain() {
);
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
expect
(
strategy
.
domElement
,
null
);
expect
((
textField
.
editableElement
as
dynamic
).
value
,
'bye'
);
expect
(
textField
.
editableElement
.
getAttribute
(
'aria-label'
),
'farewell'
);
...
...
@@ -158,6 +160,8 @@ void testMain() {
..
semanticsEnabled
=
true
;
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
strategy
.
enable
(
singlelineConfig
,
onChange:
(
_
)
{},
...
...
@@ -170,11 +174,13 @@ void testMain() {
final
TextField
textField
=
textFieldSemantics
.
debugRoleManagerFor
(
Role
.
textField
)
as
TextField
;
expect
(
textField
.
editableElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
// The input should not refocus after blur.
textField
.
editableElement
.
blur
();
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
strategy
.
disable
();
semantics
().
semanticsEnabled
=
false
;
});
...
...
@@ -199,17 +205,19 @@ void testMain() {
isFocused:
true
,
);
expect
(
strategy
.
domElement
,
isNotNull
);
expect
(
html
.
document
.
activeElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
strategy
.
disable
();
expect
(
strategy
.
domElement
,
isNull
);
// It doesn't remove the DOM element.
final
TextField
textField
=
textFieldSemantics
.
debugRoleManagerFor
(
Role
.
textField
)
as
TextField
;
expect
(
html
.
document
.
body
!
.
contains
(
textField
.
editableElement
),
isTrue
);
expect
(
appShadowRoot
.
contains
(
textField
.
editableElement
),
isTrue
);
// Editing element is not enabled.
expect
(
strategy
.
isEnabled
,
isFalse
);
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
semantics
().
semanticsEnabled
=
false
;
});
...
...
@@ -229,11 +237,13 @@ void testMain() {
isFocused:
true
,
);
expect
(
strategy
.
domElement
,
isNotNull
);
expect
(
html
.
document
.
activeElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
// Blur the element without telling the framework.
strategy
.
activeDomElement
.
blur
();
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
// The input will have focus after editing state is set and semantics updated.
strategy
.
setEditingState
(
EditingState
(
text:
'foo'
));
...
...
@@ -251,7 +261,8 @@ void testMain() {
value:
'hello'
,
isFocused:
true
,
);
expect
(
html
.
document
.
activeElement
,
strategy
.
domElement
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
strategy
.
disable
();
semantics
().
semanticsEnabled
=
false
;
...
...
@@ -274,7 +285,10 @@ void testMain() {
);
final
html
.
TextAreaElement
textArea
=
strategy
.
domElement
as
html
.
TextAreaElement
;
expect
(
html
.
document
.
activeElement
,
textArea
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
strategy
.
domElement
);
strategy
.
enable
(
singlelineConfig
,
onChange:
(
_
)
{},
...
...
@@ -283,10 +297,11 @@ void testMain() {
textArea
.
blur
();
expect
(
html
.
document
.
activeElement
,
html
.
document
.
body
);
expect
(
appShadowRoot
.
activeElement
,
null
);
strategy
.
disable
();
// It doesn't remove the textarea from the DOM.
expect
(
html
.
document
.
body
!
.
contains
(
textArea
),
isTrue
);
expect
(
appShadowRoot
.
contains
(
textArea
),
isTrue
);
// Editing element is not enabled.
expect
(
strategy
.
isEnabled
,
isFalse
);
semantics
().
semanticsEnabled
=
false
;
...
...
@@ -376,12 +391,14 @@ void testMain() {
final
SemanticsTester
tester
=
SemanticsTester
(
semantics
());
createTwoFieldSemantics
(
tester
,
focusFieldId:
1
);
expect
(
tester
.
apply
().
length
,
3
);
expect
(
html
.
document
.
activeElement
,
tester
.
getTextField
(
1
).
editableElement
);
expect
(
html
.
document
.
activeElement
,
domRenderer
.
glassPaneElement
);
expect
(
appShadowRoot
.
activeElement
,
tester
.
getTextField
(
1
).
editableElement
);
expect
(
strategy
.
domElement
,
tester
.
getTextField
(
1
).
editableElement
);
createTwoFieldSemantics
(
tester
,
focusFieldId:
2
);
expect
(
tester
.
apply
().
length
,
3
);
expect
(
html
.
documen
t
.
activeElement
,
tester
.
getTextField
(
2
).
editableElement
);
expect
(
appShadowRoo
t
.
activeElement
,
tester
.
getTextField
(
2
).
editableElement
);
expect
(
strategy
.
domElement
,
tester
.
getTextField
(
2
).
editableElement
);
}
...
...
lib/web_ui/test/engine/surface/platform_view_test.dart
浏览文件 @
b5f8141e
...
...
@@ -69,14 +69,11 @@ void testMain() {
});
group
(
'createElement'
,
()
{
test
(
'
adds reset to stylesheet
'
,
()
{
test
(
'
creates slot element that can receive pointer events
'
,
()
{
final
element
=
view
.
createElement
();
_assertShadowRootStylesheetContains
(
element
,
'all: initial;'
);
});
test
(
'creates element transparent to "cursor" property'
,
()
{
final
element
=
view
.
createElement
();
_assertShadowRootStylesheetContains
(
element
,
'cursor: inherit;'
);
expect
(
element
.
tagName
,
equalsIgnoringCase
(
'flt-platform-view-slot'
));
expect
(
element
.
style
.
pointerEvents
,
'auto'
);
});
});
});
...
...
@@ -98,14 +95,3 @@ Future<void> _createPlatformView(int id, String viewType) {
);
return
completer
.
future
;
}
void
_assertShadowRootStylesheetContains
(
html
.
Element
element
,
String
rule
)
{
final
shadow
=
element
.
shadowRoot
;
expect
(
shadow
,
isNotNull
);
final
html
.
StyleElement
style
=
shadow
.
children
.
first
;
expect
(
style
,
isNotNull
);
expect
(
style
.
innerHtml
,
contains
(
rule
));
}
lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart
浏览文件 @
b5f8141e
...
...
@@ -27,13 +27,13 @@ void testMain() async {
// Create a <flt-scene> element to make sure our CSS reset applies correctly.
final
html
.
Element
testScene
=
html
.
Element
.
tag
(
'flt-scene'
);
testScene
.
append
(
canvas
.
rootElement
);
html
.
document
.
querySelector
(
'flt-scene-host'
)!.
append
(
testScene
);
domRenderer
.
glassPaneShadow
!
.
querySelector
(
'flt-scene-host'
)!.
append
(
testScene
);
}
setUpStableTestFonts
();
tearDown
(()
{
html
.
document
.
querySelector
(
'flt-scene'
)!
.
remove
();
domRenderer
.
glassPaneShadow
?.
querySelector
(
'flt-scene'
)?
.
remove
();
});
/// Draws several lines, some aligned precisely with the pixel grid, and some
...
...
@@ -249,7 +249,7 @@ void testMain() async {
final
html
.
Element
sceneElement
=
scene
.
webOnlyRootElement
!;
sceneElement
.
querySelector
(
'flt-clip'
)!.
append
(
canvas
.
rootElement
);
html
.
document
.
querySelector
(
'flt-scene-host'
)!.
append
(
sceneElement
);
domRenderer
.
glassPaneShadow
!
.
querySelector
(
'flt-scene-host'
)!.
append
(
sceneElement
);
await
matchGoldenFile
(
'bitmap_canvas_draws_text_on_top_of_canvas.png'
,
...
...
lib/web_ui/test/text_editing_test.dart
浏览文件 @
b5f8141e
此差异已折叠。
点击以展开。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录