Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b61160a

Browse files
authored
[web] Apply global styles before inserting the DOM element (#48027)
The current way we are doing global styles (using `CSSStyleSheet`) forces us to insert the DOM element into the document before we attach any styles to the element. That restriction goes away if we append the styles as `TextNode`s into the `<style>` element. Now with the movement towards `DomManager`, I would like to be able to create the entire DOM tree of the Flutter View (including all styles) before we insert it into the document. Part of flutter/flutter#134443
1 parent b13ca87 commit b61160a

File tree

2 files changed

+78
-95
lines changed

2 files changed

+78
-95
lines changed

lib/web_ui/lib/src/engine/view_embedder/style_manager.dart

Lines changed: 73 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -78,106 +78,86 @@ void applyGlobalCssRulesToSheet(
7878
String cssSelectorPrefix = '',
7979
required String defaultCssFont,
8080
}) {
81-
// TODO(web): use more efficient CSS selectors; descendant selectors are slow.
82-
// More info: https://csswizardry.com/2011/09/writing-efficient-css-selectors
83-
84-
assert(styleElement.sheet != null);
85-
final DomCSSStyleSheet sheet = styleElement.sheet! as DomCSSStyleSheet;
86-
87-
// Fixes #115216 by ensuring that our parameters only affect the flt-scene-host children.
88-
sheet.insertRule('''
89-
$cssSelectorPrefix ${DomManager.sceneHostTagName} {
90-
font: $defaultCssFont;
91-
}
92-
''', sheet.cssRules.length);
81+
styleElement.appendText(
82+
// Fixes #115216 by ensuring that our parameters only affect the flt-scene-host children.
83+
'$cssSelectorPrefix ${DomManager.sceneHostTagName} {'
84+
' font: $defaultCssFont;'
85+
'}'
86+
87+
// This undoes browser's default painting and layout attributes of range
88+
// input, which is used in semantics.
89+
'$cssSelectorPrefix flt-semantics input[type=range] {'
90+
' appearance: none;'
91+
' -webkit-appearance: none;'
92+
' width: 100%;'
93+
' position: absolute;'
94+
' border: none;'
95+
' top: 0;'
96+
' right: 0;'
97+
' bottom: 0;'
98+
' left: 0;'
99+
'}'
100+
101+
// The invisible semantic text field may have a visible cursor and selection
102+
// highlight. The following 2 CSS rules force everything to be transparent.
103+
'$cssSelectorPrefix input::selection {'
104+
' background-color: transparent;'
105+
'}'
106+
'$cssSelectorPrefix textarea::selection {'
107+
' background-color: transparent;'
108+
'}'
109+
110+
'$cssSelectorPrefix flt-semantics input,'
111+
'$cssSelectorPrefix flt-semantics textarea,'
112+
'$cssSelectorPrefix flt-semantics [contentEditable="true"] {'
113+
' caret-color: transparent;'
114+
'}'
115+
116+
// Hide placeholder text
117+
'$cssSelectorPrefix .flt-text-editing::placeholder {'
118+
' opacity: 0;'
119+
'}',
120+
);
93121

94122
// By default on iOS, Safari would highlight the element that's being tapped
95123
// on using gray background. This CSS rule disables that.
96124
if (isSafari) {
97-
sheet.insertRule('''
98-
$cssSelectorPrefix * {
99-
-webkit-tap-highlight-color: transparent;
100-
}
101-
''', sheet.cssRules.length);
125+
styleElement.appendText(
126+
'$cssSelectorPrefix * {'
127+
' -webkit-tap-highlight-color: transparent;'
128+
'}'
129+
130+
'$cssSelectorPrefix flt-semantics input[type=range]::-webkit-slider-thumb {'
131+
' -webkit-appearance: none;'
132+
'}'
133+
);
102134
}
103135

104136
if (isFirefox) {
105137
// For firefox set line-height, otherwise text at same font-size will
106138
// measure differently in ruler.
107139
//
108140
// - See: https://github.com/flutter/flutter/issues/44803
109-
sheet.insertRule('''
110-
$cssSelectorPrefix flt-paragraph,
111-
$cssSelectorPrefix flt-span {
112-
line-height: 100%;
113-
}
114-
''', sheet.cssRules.length);
115-
}
116-
117-
// This undoes browser's default painting and layout attributes of range
118-
// input, which is used in semantics.
119-
sheet.insertRule('''
120-
$cssSelectorPrefix flt-semantics input[type=range] {
121-
appearance: none;
122-
-webkit-appearance: none;
123-
width: 100%;
124-
position: absolute;
125-
border: none;
126-
top: 0;
127-
right: 0;
128-
bottom: 0;
129-
left: 0;
130-
}
131-
''', sheet.cssRules.length);
132-
133-
if (isSafari) {
134-
sheet.insertRule('''
135-
$cssSelectorPrefix flt-semantics input[type=range]::-webkit-slider-thumb {
136-
-webkit-appearance: none;
137-
}
138-
''', sheet.cssRules.length);
141+
styleElement.appendText(
142+
'$cssSelectorPrefix flt-paragraph,'
143+
'$cssSelectorPrefix flt-span {'
144+
' line-height: 100%;'
145+
'}'
146+
);
139147
}
140148

141-
// The invisible semantic text field may have a visible cursor and selection
142-
// highlight. The following 2 CSS rules force everything to be transparent.
143-
sheet.insertRule('''
144-
$cssSelectorPrefix input::selection {
145-
background-color: transparent;
146-
}
147-
''', sheet.cssRules.length);
148-
sheet.insertRule('''
149-
$cssSelectorPrefix textarea::selection {
150-
background-color: transparent;
151-
}
152-
''', sheet.cssRules.length);
153-
154-
sheet.insertRule('''
155-
$cssSelectorPrefix flt-semantics input,
156-
$cssSelectorPrefix flt-semantics textarea,
157-
$cssSelectorPrefix flt-semantics [contentEditable="true"] {
158-
caret-color: transparent;
159-
}
160-
''', sheet.cssRules.length);
161-
162-
// Hide placeholder text
163-
sheet.insertRule('''
164-
$cssSelectorPrefix .flt-text-editing::placeholder {
165-
opacity: 0;
166-
}
167-
''', sheet.cssRules.length);
168-
169149
// This CSS makes the autofill overlay transparent in order to prevent it
170150
// from overlaying on top of Flutter-rendered text inputs.
171151
// See: https://github.com/flutter/flutter/issues/118337.
172152
if (browserHasAutofillOverlay()) {
173-
sheet.insertRule('''
174-
$cssSelectorPrefix .transparentTextEditing:-webkit-autofill,
175-
$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:hover,
176-
$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:focus,
177-
$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:active {
178-
opacity: 0 !important;
179-
}
180-
''', sheet.cssRules.length);
153+
styleElement.appendText(
154+
'$cssSelectorPrefix .transparentTextEditing:-webkit-autofill,'
155+
'$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:hover,'
156+
'$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:focus,'
157+
'$cssSelectorPrefix .transparentTextEditing:-webkit-autofill:active {'
158+
' opacity: 0 !important;'
159+
'}'
160+
);
181161
}
182162

183163
// Removes password reveal icon for text inputs in Edge browsers.
@@ -189,22 +169,22 @@ void applyGlobalCssRulesToSheet(
189169
// so the below will throw an exception (because only real Edge understands
190170
// the ::-ms-reveal pseudo-selector).
191171
try {
192-
sheet.insertRule('''
193-
$cssSelectorPrefix input::-ms-reveal {
194-
display: none;
195-
}
196-
''', sheet.cssRules.length);
172+
styleElement.appendText(
173+
'$cssSelectorPrefix input::-ms-reveal {'
174+
' display: none;'
175+
'}'
176+
);
197177
} on DomException catch (e) {
198178
// Browsers that don't understand ::-ms-reveal throw a DOMException
199179
// of type SyntaxError.
200180
domWindow.console.warn(e);
201181
// Add a fake rule if our code failed because we're under testing
202182
assert(() {
203-
sheet.insertRule('''
204-
$cssSelectorPrefix input.fallback-for-fakey-browser-in-ci {
205-
display: none;
206-
}
207-
''', sheet.cssRules.length);
183+
styleElement.appendText(
184+
'$cssSelectorPrefix input.fallback-for-fakey-browser-in-ci {'
185+
' display: none;'
186+
'}'
187+
);
208188
return true;
209189
}());
210190
}

lib/web_ui/test/engine/global_styles_test.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ void testMain() {
1717

1818
setUp(() {
1919
styleElement = createDomHTMLStyleElement(null);
20-
domDocument.body!.append(styleElement);
2120
applyGlobalCssRulesToSheet(
2221
styleElement,
2322
defaultCssFont: _kDefaultCssFont,
@@ -101,6 +100,7 @@ bool hasCssRule(
101100
required String selector,
102101
required String declaration,
103102
}) {
103+
domDocument.body!.append(styleElement);
104104
assert(styleElement.sheet != null);
105105

106106
// regexr.com/740ff
@@ -110,7 +110,10 @@ bool hasCssRule(
110110
final DomCSSStyleSheet sheet = styleElement.sheet! as DomCSSStyleSheet;
111111

112112
// Check that the cssText of any rule matches the ruleLike RegExp.
113-
return sheet.cssRules
113+
final bool result = sheet.cssRules
114114
.map((DomCSSRule rule) => rule.cssText)
115115
.any((String rule) => ruleLike.hasMatch(rule));
116+
117+
styleElement.remove();
118+
return result;
116119
}

0 commit comments

Comments
 (0)